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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobert Speicher <rspeicher@gmail.com>2021-01-20 22:34:23 +0300
committerRobert Speicher <rspeicher@gmail.com>2021-01-20 22:34:23 +0300
commit6438df3a1e0fb944485cebf07976160184697d72 (patch)
tree00b09bfd170e77ae9391b1a2f5a93ef6839f2597 /spec/lib/gitlab
parent42bcd54d971da7ef2854b896a7b34f4ef8601067 (diff)
Add latest changes from gitlab-org/gitlab@13-8-stable-eev13.8.0-rc42
Diffstat (limited to 'spec/lib/gitlab')
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_deployed_to_production_spec.rb7
-rw-r--r--spec/lib/gitlab/api_authentication/builder_spec.rb76
-rw-r--r--spec/lib/gitlab/api_authentication/sent_through_builder_spec.rb17
-rw-r--r--spec/lib/gitlab/api_authentication/token_locator_spec.rb55
-rw-r--r--spec/lib/gitlab/api_authentication/token_resolver_spec.rb117
-rw-r--r--spec/lib/gitlab/api_authentication/token_type_builder_spec.rb16
-rw-r--r--spec/lib/gitlab/asset_proxy_spec.rb4
-rw-r--r--spec/lib/gitlab/auth/auth_finders_spec.rb58
-rw-r--r--spec/lib/gitlab/auth/current_user_mode_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/ldap/config_spec.rb22
-rw-r--r--spec/lib/gitlab/auth/request_authenticator_spec.rb5
-rw-r--r--spec/lib/gitlab/auth_spec.rb5
-rw-r--r--spec/lib/gitlab/background_migration/backfill_artifact_expiry_date_spec.rb82
-rw-r--r--spec/lib/gitlab/background_migration/copy_column_using_background_migration_job_spec.rb91
-rw-r--r--spec/lib/gitlab/background_migration/populate_finding_uuid_for_vulnerability_feedback_spec.rb113
-rw-r--r--spec/lib/gitlab/background_migration/remove_duplicate_services_spec.rb121
-rw-r--r--spec/lib/gitlab/checks/diff_check_spec.rb97
-rw-r--r--spec/lib/gitlab/ci/config/entry/artifacts_spec.rb17
-rw-r--r--spec/lib/gitlab/ci/config/entry/variables_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/external/file/local_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/config/external/file/project_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/config/external/mapper_spec.rb126
-rw-r--r--spec/lib/gitlab/ci/config/external/processor_spec.rb13
-rw-r--r--spec/lib/gitlab/ci/config_spec.rb24
-rw-r--r--spec/lib/gitlab/ci/lint_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/parsers/coverage/cobertura_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/build_spec.rb114
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/command_spec.rb26
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/seed_block_spec.rb12
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb39
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/template_usage_spec.rb37
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/build_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/pipeline_spec.rb3
-rw-r--r--spec/lib/gitlab/ci/reports/test_failure_history_spec.rb13
-rw-r--r--spec/lib/gitlab/ci/status/group/factory_spec.rb5
-rw-r--r--spec/lib/gitlab/ci/syntax_templates_spec.rb25
-rw-r--r--spec/lib/gitlab/ci/templates/5_minute_production_app_ci_yaml_spec.rb49
-rw-r--r--spec/lib/gitlab/ci/templates/AWS/deploy_ecs_gitlab_ci_yaml_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/templates/Jobs/build_gitlab_ci_yaml_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/templates/Jobs/code_quality_gitlab_ci_yaml_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/templates/Jobs/test_gitlab_ci_yaml_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/templates/Terraform/base_gitlab_ci_yaml_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/templates/Verify/load_performance_testing_gitlab_ci_yaml_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/templates/flutter_gitlab_ci_yaml_spec.rb25
-rw-r--r--spec/lib/gitlab/ci/templates/npm_spec.rb3
-rw-r--r--spec/lib/gitlab/ci/templates/terraform_latest_gitlab_ci_yaml_spec.rb3
-rw-r--r--spec/lib/gitlab/ci/variables/collection/sorted_spec.rb251
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb34
-rw-r--r--spec/lib/gitlab/composer/version_index_spec.rb49
-rw-r--r--spec/lib/gitlab/config/entry/composable_hash_spec.rb2
-rw-r--r--spec/lib/gitlab/conflict/file_spec.rb32
-rw-r--r--spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb49
-rw-r--r--spec/lib/gitlab/cycle_analytics/code_event_fetcher_spec.rb13
-rw-r--r--spec/lib/gitlab/cycle_analytics/code_stage_spec.rb129
-rw-r--r--spec/lib/gitlab/cycle_analytics/events_spec.rb182
-rw-r--r--spec/lib/gitlab/cycle_analytics/issue_event_fetcher_spec.rb9
-rw-r--r--spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb136
-rw-r--r--spec/lib/gitlab/cycle_analytics/plan_event_fetcher_spec.rb17
-rw-r--r--spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb116
-rw-r--r--spec/lib/gitlab/cycle_analytics/production_event_fetcher_spec.rb9
-rw-r--r--spec/lib/gitlab/cycle_analytics/review_event_fetcher_spec.rb9
-rw-r--r--spec/lib/gitlab/cycle_analytics/review_stage_spec.rb90
-rw-r--r--spec/lib/gitlab/cycle_analytics/staging_event_fetcher_spec.rb13
-rw-r--r--spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb99
-rw-r--r--spec/lib/gitlab/cycle_analytics/test_event_fetcher_spec.rb13
-rw-r--r--spec/lib/gitlab/cycle_analytics/test_stage_spec.rb57
-rw-r--r--spec/lib/gitlab/danger/base_linter_spec.rb53
-rw-r--r--spec/lib/gitlab/danger/changelog_spec.rb71
-rw-r--r--spec/lib/gitlab/danger/helper_spec.rb56
-rw-r--r--spec/lib/gitlab/danger/roulette_spec.rb63
-rw-r--r--spec/lib/gitlab/danger/teammate_spec.rb8
-rw-r--r--spec/lib/gitlab/danger/title_linting_spec.rb56
-rw-r--r--spec/lib/gitlab/danger/weightage/maintainers_spec.rb34
-rw-r--r--spec/lib/gitlab/danger/weightage/reviewers_spec.rb63
-rw-r--r--spec/lib/gitlab/data_builder/build_spec.rb1
-rw-r--r--spec/lib/gitlab/data_builder/pipeline_spec.rb1
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb190
-rw-r--r--spec/lib/gitlab/database/partitioning/partition_creator_spec.rb2
-rw-r--r--spec/lib/gitlab/database/partitioning/replace_table_spec.rb2
-rw-r--r--spec/lib/gitlab/database/partitioning_migration_helpers/foreign_key_helpers_spec.rb2
-rw-r--r--spec/lib/gitlab/database/partitioning_migration_helpers/index_helpers_spec.rb2
-rw-r--r--spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb139
-rw-r--r--spec/lib/gitlab/database/postgres_hll/batch_distinct_counter_spec.rb83
-rw-r--r--spec/lib/gitlab/database/postgres_hll/buckets_spec.rb33
-rw-r--r--spec/lib/gitlab/database/reindexing/coordinator_spec.rb82
-rw-r--r--spec/lib/gitlab/database/reindexing/grafana_notifier_spec.rb139
-rw-r--r--spec/lib/gitlab/database/reindexing/index_selection_spec.rb2
-rw-r--r--spec/lib/gitlab/database/reindexing/reindex_action_spec.rb112
-rw-r--r--spec/lib/gitlab/database/reindexing_spec.rb9
-rw-r--r--spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb30
-rw-r--r--spec/lib/gitlab/diff/position_spec.rb58
-rw-r--r--spec/lib/gitlab/email/handler/create_note_handler_spec.rb234
-rw-r--r--spec/lib/gitlab/email/handler/create_note_on_issuable_handler_spec.rb61
-rw-r--r--spec/lib/gitlab/email/handler/service_desk_handler_spec.rb10
-rw-r--r--spec/lib/gitlab/email/handler_spec.rb5
-rw-r--r--spec/lib/gitlab/error_tracking_spec.rb21
-rw-r--r--spec/lib/gitlab/experimentation/controller_concern_spec.rb10
-rw-r--r--spec/lib/gitlab/experimentation/experiment_spec.rb6
-rw-r--r--spec/lib/gitlab/experimentation_spec.rb2
-rw-r--r--spec/lib/gitlab/faraday/error_callback_spec.rb59
-rw-r--r--spec/lib/gitlab/git/changed_path_spec.rb29
-rw-r--r--spec/lib/gitlab/git/diff_spec.rb2
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb31
-rw-r--r--spec/lib/gitlab/git/wiki_page_version_spec.rb28
-rw-r--r--spec/lib/gitlab/git_access_snippet_spec.rb89
-rw-r--r--spec/lib/gitlab/git_access_spec.rb60
-rw-r--r--spec/lib/gitlab/gitaly_client/commit_service_spec.rb6
-rw-r--r--spec/lib/gitlab/gitaly_client/repository_service_spec.rb5
-rw-r--r--spec/lib/gitlab/github_import/importer/repository_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/gitpod_spec.rb73
-rw-r--r--spec/lib/gitlab/graphql/batch_key_spec.rb78
-rw-r--r--spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb41
-rw-r--r--spec/lib/gitlab/graphql/queries_spec.rb343
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml4
-rw-r--r--spec/lib/gitlab/kubernetes/cilium_network_policy_spec.rb24
-rw-r--r--spec/lib/gitlab/kubernetes/kubectl_cmd_spec.rb5
-rw-r--r--spec/lib/gitlab/kubernetes/pod_cmd_spec.rb14
-rw-r--r--spec/lib/gitlab/metrics/samplers/action_cable_sampler_spec.rb10
-rw-r--r--spec/lib/gitlab/metrics/samplers/database_sampler_spec.rb10
-rw-r--r--spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb10
-rw-r--r--spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb12
-rw-r--r--spec/lib/gitlab/metrics/samplers/threads_sampler_spec.rb10
-rw-r--r--spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/system_spec.rb34
-rw-r--r--spec/lib/gitlab/middleware/multipart/handler_for_jwt_params_spec.rb53
-rw-r--r--spec/lib/gitlab/middleware/multipart_spec.rb (renamed from spec/lib/gitlab/middleware/multipart_with_handler_for_jwt_params_spec.rb)20
-rw-r--r--spec/lib/gitlab/middleware/multipart_with_handler_spec.rb196
-rw-r--r--spec/lib/gitlab/path_regex_spec.rb6
-rw-r--r--spec/lib/gitlab/performance_bar/redis_adapter_when_peek_enabled_spec.rb1
-rw-r--r--spec/lib/gitlab/project_template_spec.rb1
-rw-r--r--spec/lib/gitlab/prometheus/internal_spec.rb48
-rw-r--r--spec/lib/gitlab/rack_attack_spec.rb212
-rw-r--r--spec/lib/gitlab/search_results_spec.rb39
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/admin_mode/client_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/admin_mode/server_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb9
-rw-r--r--spec/lib/gitlab/template/gitlab_ci_syntax_yml_template_spec.rb17
-rw-r--r--spec/lib/gitlab/throttle_spec.rb28
-rw-r--r--spec/lib/gitlab/tracking/standard_context_spec.rb55
-rw-r--r--spec/lib/gitlab/tracking_spec.rb43
-rw-r--r--spec/lib/gitlab/url_builder_spec.rb2
-rw-r--r--spec/lib/gitlab/usage/metric_definition_spec.rb123
-rw-r--r--spec/lib/gitlab/usage/metric_spec.rb29
-rw-r--r--spec/lib/gitlab/usage_data_counters/ci_template_unique_counter_spec.rb31
-rw-r--r--spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb156
-rw-r--r--spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb151
-rw-r--r--spec/lib/gitlab/usage_data_counters/package_event_counter_spec.rb (renamed from spec/lib/gitlab/usage_data_counters/guest_package_event_counter_spec.rb)8
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb6
-rw-r--r--spec/lib/gitlab/user_access_spec.rb32
-rw-r--r--spec/lib/gitlab/utils/usage_data_spec.rb112
-rw-r--r--spec/lib/gitlab/utils_spec.rb17
-rw-r--r--spec/lib/gitlab/uuid_spec.rb19
-rw-r--r--spec/lib/gitlab/visibility_level_spec.rb48
156 files changed, 4426 insertions, 2436 deletions
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_deployed_to_production_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_deployed_to_production_spec.rb
new file mode 100644
index 00000000000..93e588675d3
--- /dev/null
+++ b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_deployed_to_production_spec.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Analytics::CycleAnalytics::StageEvents::IssueDeployedToProduction do
+ it_behaves_like 'value stream analytics event'
+end
diff --git a/spec/lib/gitlab/api_authentication/builder_spec.rb b/spec/lib/gitlab/api_authentication/builder_spec.rb
new file mode 100644
index 00000000000..e241aa77805
--- /dev/null
+++ b/spec/lib/gitlab/api_authentication/builder_spec.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+RSpec.describe Gitlab::APIAuthentication::Builder do
+ describe '#build' do
+ shared_examples 'builds the correct result' do |token_type:, sent_through:, builds:|
+ context "with #{token_type.size} token type(s) and #{sent_through.size} sent through(s)" do
+ it 'works when passed together' do
+ strategies = described_class.new.build { |allow| allow.token_types(*token_type).sent_through(*sent_through) }
+
+ expect(strategies).to eq(builds)
+ end
+
+ it 'works when token types are passed separately' do
+ strategies = described_class.new.build { |allow| token_type.each { |t| allow.token_types(t).sent_through(*sent_through) } }
+
+ expect(strategies).to eq(builds)
+ end
+
+ it 'works when sent throughs are passed separately' do
+ strategies = described_class.new.build { |allow| sent_through.each { |s| allow.token_types(*token_type).sent_through(s) } }
+
+ expect(strategies).to eq(builds)
+ end
+
+ it 'works when token types and sent throughs are passed separately' do
+ strategies = described_class.new.build { |allow| token_type.each { |t| sent_through.each { |s| allow.token_types(t).sent_through(s) } } }
+
+ expect(strategies).to eq(builds)
+ end
+ end
+ end
+
+ it_behaves_like 'builds the correct result',
+ token_type: [:pat],
+ sent_through: [:basic],
+ builds: { basic: [:pat] }
+
+ it_behaves_like 'builds the correct result',
+ token_type: [:pat],
+ sent_through: [:basic, :oauth],
+ builds: { basic: [:pat], oauth: [:pat] }
+
+ it_behaves_like 'builds the correct result',
+ token_type: [:pat, :job],
+ sent_through: [:basic],
+ builds: { basic: [:pat, :job] }
+
+ it_behaves_like 'builds the correct result',
+ token_type: [:pat, :job],
+ sent_through: [:basic, :oauth],
+ builds: { basic: [:pat, :job], oauth: [:pat, :job] }
+
+ context 'with a complex auth strategy' do
+ it 'builds the correct result' do
+ strategies = described_class.new.build do |allow|
+ allow.token_types(:pat, :job, :deploy).sent_through(:http_basic, :oauth)
+ allow.token_types(:pat).sent_through(:http_private, :query_private)
+ allow.token_types(:oauth2).sent_through(:http_bearer, :query_access)
+ end
+
+ expect(strategies).to eq({
+ http_basic: [:pat, :job, :deploy],
+ oauth: [:pat, :job, :deploy],
+
+ http_private: [:pat],
+ query_private: [:pat],
+
+ http_bearer: [:oauth2],
+ query_access: [:oauth2]
+ })
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/api_authentication/sent_through_builder_spec.rb b/spec/lib/gitlab/api_authentication/sent_through_builder_spec.rb
new file mode 100644
index 00000000000..845e317f3aa
--- /dev/null
+++ b/spec/lib/gitlab/api_authentication/sent_through_builder_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+RSpec.describe Gitlab::APIAuthentication::SentThroughBuilder do
+ describe '#sent_through' do
+ let(:resolvers) { Array.new(3) { double } }
+ let(:locators) { Array.new(3) { double } }
+
+ it 'adds a strategy for each of locators x resolvers' do
+ strategies = locators.to_h { |l| [l, []] }
+ described_class.new(strategies, resolvers).sent_through(*locators)
+
+ expect(strategies).to eq(locators.to_h { |l| [l, resolvers] })
+ end
+ end
+end
diff --git a/spec/lib/gitlab/api_authentication/token_locator_spec.rb b/spec/lib/gitlab/api_authentication/token_locator_spec.rb
new file mode 100644
index 00000000000..68ce48a70ea
--- /dev/null
+++ b/spec/lib/gitlab/api_authentication/token_locator_spec.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::APIAuthentication::TokenLocator do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project, reload: true) { create(:project, :public) }
+ let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
+ let_it_be(:ci_job) { create(:ci_build, project: project, user: user, status: :running) }
+ let_it_be(:ci_job_done) { create(:ci_build, project: project, user: user, status: :success) }
+ let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
+
+ describe '.new' do
+ context 'with a valid type' do
+ it 'creates a new instance' do
+ expect(described_class.new(:http_basic_auth)).to be_a(described_class)
+ end
+ end
+
+ context 'with an invalid type' do
+ it 'raises ActiveModel::ValidationError' do
+ expect { described_class.new(:not_a_real_locator) }.to raise_error(ActiveModel::ValidationError)
+ end
+ end
+ end
+
+ describe '#extract' do
+ let(:locator) { described_class.new(type) }
+
+ subject { locator.extract(request) }
+
+ context 'with :http_basic_auth' do
+ let(:type) { :http_basic_auth }
+
+ context 'without credentials' do
+ let(:request) { double(authorization: nil) }
+
+ it 'returns nil' do
+ expect(subject).to be(nil)
+ end
+ end
+
+ context 'with credentials' do
+ let(:username) { 'foo' }
+ let(:password) { 'bar' }
+ let(:request) { double(authorization: "Basic #{::Base64.strict_encode64("#{username}:#{password}")}") }
+
+ it 'returns the credentials' do
+ expect(subject.username).to eq(username)
+ expect(subject.password).to eq(password)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/api_authentication/token_resolver_spec.rb b/spec/lib/gitlab/api_authentication/token_resolver_spec.rb
new file mode 100644
index 00000000000..0028fb080ac
--- /dev/null
+++ b/spec/lib/gitlab/api_authentication/token_resolver_spec.rb
@@ -0,0 +1,117 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::APIAuthentication::TokenResolver do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project, reload: true) { create(:project, :public) }
+ let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
+ let_it_be(:ci_job) { create(:ci_build, project: project, user: user, status: :running) }
+ let_it_be(:ci_job_done) { create(:ci_build, project: project, user: user, status: :success) }
+ let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
+
+ shared_examples 'an authorized request' do
+ it 'returns the correct token' do
+ expect(subject).to eq(token)
+ end
+ end
+
+ shared_examples 'an unauthorized request' do
+ it 'raises an error' do
+ expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError)
+ end
+ end
+
+ shared_examples 'an anoymous request' do
+ it 'returns nil' do
+ expect(subject).to eq(nil)
+ end
+ end
+
+ describe '.new' do
+ context 'with a valid type' do
+ it 'creates a new instance' do
+ expect(described_class.new(:personal_access_token)).to be_a(described_class)
+ end
+ end
+
+ context 'with an invalid type' do
+ it 'raises a validation error' do
+ expect { described_class.new(:not_a_real_locator) }.to raise_error(ActiveModel::ValidationError)
+ end
+ end
+ end
+
+ describe '#resolve' do
+ let(:resolver) { described_class.new(type) }
+
+ subject { resolver.resolve(raw) }
+
+ context 'with :personal_access_token' do
+ let(:type) { :personal_access_token }
+ let(:token) { personal_access_token }
+
+ context 'with valid credentials' do
+ let(:raw) { username_and_password(user.username, token.token) }
+
+ it_behaves_like 'an authorized request'
+ end
+
+ context 'with an invalid username' do
+ let(:raw) { username_and_password("not-my-#{user.username}", token.token) }
+
+ it_behaves_like 'an unauthorized request'
+ end
+ end
+
+ context 'with :job_token' do
+ let(:type) { :job_token }
+ let(:token) { ci_job }
+
+ context 'with valid credentials' do
+ let(:raw) { username_and_password(Gitlab::Auth::CI_JOB_USER, token.token) }
+
+ it_behaves_like 'an authorized request'
+ end
+
+ context 'when the job is not running' do
+ let(:raw) { username_and_password(Gitlab::Auth::CI_JOB_USER, ci_job_done.token) }
+
+ it_behaves_like 'an unauthorized request'
+ end
+
+ context 'with the wrong username' do
+ let(:raw) { username_and_password("not-#{Gitlab::Auth::CI_JOB_USER}", nil) }
+
+ it_behaves_like 'an anoymous request'
+ end
+
+ context 'with an invalid job token' do
+ let(:raw) { username_and_password(Gitlab::Auth::CI_JOB_USER, "not a valid CI job token") }
+
+ it_behaves_like 'an unauthorized request'
+ end
+ end
+
+ context 'with :deploy_token' do
+ let(:type) { :deploy_token }
+ let(:token) { deploy_token }
+
+ context 'with a valid deploy token' do
+ let(:raw) { username_and_password(token.username, token.token) }
+
+ it_behaves_like 'an authorized request'
+ end
+
+ context 'with an invalid username' do
+ let(:raw) { username_and_password("not-my-#{token.username}", token.token) }
+
+ it_behaves_like 'an unauthorized request'
+ end
+ end
+ end
+
+ def username_and_password(username, password)
+ ::Gitlab::APIAuthentication::TokenLocator::UsernameAndPassword.new(username, password)
+ end
+end
diff --git a/spec/lib/gitlab/api_authentication/token_type_builder_spec.rb b/spec/lib/gitlab/api_authentication/token_type_builder_spec.rb
new file mode 100644
index 00000000000..fbca62c9a42
--- /dev/null
+++ b/spec/lib/gitlab/api_authentication/token_type_builder_spec.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+RSpec.describe Gitlab::APIAuthentication::TokenTypeBuilder do
+ describe '#token_types' do
+ it 'passes strategies and resolvers to SentThroughBuilder' do
+ strategies = double
+ resolvers = Array.new(3) { double }
+ retval = double
+ expect(Gitlab::APIAuthentication::SentThroughBuilder).to receive(:new).with(strategies, resolvers).and_return(retval)
+
+ expect(described_class.new(strategies).token_types(*resolvers)).to be(retval)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/asset_proxy_spec.rb b/spec/lib/gitlab/asset_proxy_spec.rb
index 73b101c0dd8..7d7952d5741 100644
--- a/spec/lib/gitlab/asset_proxy_spec.rb
+++ b/spec/lib/gitlab/asset_proxy_spec.rb
@@ -17,12 +17,12 @@ RSpec.describe Gitlab::AssetProxy do
context 'when asset proxy is enabled' do
before do
- stub_asset_proxy_setting(whitelist: %w(gitlab.com *.mydomain.com))
+ stub_asset_proxy_setting(allowlist: %w(gitlab.com *.mydomain.com))
stub_asset_proxy_setting(
enabled: true,
url: 'https://assets.example.com',
secret_key: 'shared-secret',
- domain_regexp: Banzai::Filter::AssetProxyFilter.compile_whitelist(Gitlab.config.asset_proxy.whitelist)
+ domain_regexp: Banzai::Filter::AssetProxyFilter.compile_allowlist(Gitlab.config.asset_proxy.allowlist)
)
end
diff --git a/spec/lib/gitlab/auth/auth_finders_spec.rb b/spec/lib/gitlab/auth/auth_finders_spec.rb
index f927d5912bb..775f8f056b5 100644
--- a/spec/lib/gitlab/auth/auth_finders_spec.rb
+++ b/spec/lib/gitlab/auth/auth_finders_spec.rb
@@ -6,7 +6,8 @@ RSpec.describe Gitlab::Auth::AuthFinders do
include described_class
include HttpBasicAuthHelpers
- let(:user) { create(:user) }
+ # Create the feed_token and static_object_token for the user
+ let_it_be(:user) { create(:user).tap(&:feed_token).tap(&:static_object_token) }
let(:env) do
{
'rack.input' => ''
@@ -65,7 +66,7 @@ RSpec.describe Gitlab::Auth::AuthFinders do
end
describe '#find_user_from_bearer_token' do
- let(:job) { create(:ci_build, user: user) }
+ let_it_be_with_reload(:job) { create(:ci_build, user: user) }
subject { find_user_from_bearer_token }
@@ -91,7 +92,7 @@ RSpec.describe Gitlab::Auth::AuthFinders do
end
context 'with a personal access token' do
- let(:pat) { create(:personal_access_token, user: user) }
+ let_it_be(:pat) { create(:personal_access_token, user: user) }
let(:token) { pat.token }
before do
@@ -148,7 +149,7 @@ RSpec.describe Gitlab::Auth::AuthFinders do
end
it 'returns nil if valid feed_token and disabled' do
- allow(Gitlab::CurrentSettings).to receive(:disable_feed_token).and_return(true)
+ stub_application_setting(disable_feed_token: true)
set_param(:feed_token, user.feed_token)
expect(find_user_from_feed_token(:rss)).to be_nil
@@ -166,7 +167,7 @@ RSpec.describe Gitlab::Auth::AuthFinders do
end
context 'when rss_token param is provided' do
- it 'returns user if valid rssd_token' do
+ it 'returns user if valid rss_token' do
set_param(:rss_token, user.feed_token)
expect(find_user_from_feed_token(:rss)).to eq user
@@ -347,7 +348,7 @@ RSpec.describe Gitlab::Auth::AuthFinders do
end
describe '#find_user_from_access_token' do
- let(:personal_access_token) { create(:personal_access_token, user: user) }
+ let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
before do
set_header('SCRIPT_NAME', 'url.atom')
@@ -386,7 +387,7 @@ RSpec.describe Gitlab::Auth::AuthFinders do
end
context 'when using a non-prefixed access token' do
- let(:personal_access_token) { create(:personal_access_token, :no_prefix, user: user) }
+ let_it_be(:personal_access_token) { create(:personal_access_token, :no_prefix, user: user) }
it 'returns user' do
set_header('HTTP_AUTHORIZATION', "Bearer #{personal_access_token.token}")
@@ -398,7 +399,7 @@ RSpec.describe Gitlab::Auth::AuthFinders do
end
describe '#find_user_from_web_access_token' do
- let(:personal_access_token) { create(:personal_access_token, user: user) }
+ let_it_be_with_reload(:personal_access_token) { create(:personal_access_token, user: user) }
before do
set_header(described_class::PRIVATE_TOKEN_HEADER, personal_access_token.token)
@@ -449,6 +450,22 @@ RSpec.describe Gitlab::Auth::AuthFinders do
expect(find_user_from_web_access_token(:api)).to be_nil
end
+ context 'when the token has read_api scope' do
+ before do
+ personal_access_token.update!(scopes: ['read_api'])
+
+ set_header('SCRIPT_NAME', '/api/endpoint')
+ end
+
+ it 'raises InsufficientScopeError by default' do
+ expect { find_user_from_web_access_token(:api) }.to raise_error(Gitlab::Auth::InsufficientScopeError)
+ end
+
+ it 'finds the user when the read_api scope is passed' do
+ expect(find_user_from_web_access_token(:api, scopes: [:api, :read_api])).to eq(user)
+ end
+ end
+
context 'when relative_url_root is set' do
before do
stub_config_setting(relative_url_root: '/relative_root')
@@ -464,7 +481,7 @@ RSpec.describe Gitlab::Auth::AuthFinders do
end
describe '#find_personal_access_token' do
- let(:personal_access_token) { create(:personal_access_token, user: user) }
+ let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
before do
set_header('SCRIPT_NAME', 'url.atom')
@@ -534,7 +551,7 @@ RSpec.describe Gitlab::Auth::AuthFinders do
end
context 'access token is valid' do
- let(:personal_access_token) { create(:personal_access_token, user: user) }
+ let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
let(:route_authentication_setting) { { basic_auth_personal_access_token: true } }
it 'finds the token from basic auth' do
@@ -555,7 +572,7 @@ RSpec.describe Gitlab::Auth::AuthFinders do
end
context 'route_setting is not set' do
- let(:personal_access_token) { create(:personal_access_token, user: user) }
+ let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
it 'returns nil' do
auth_header_with(personal_access_token.token)
@@ -565,7 +582,7 @@ RSpec.describe Gitlab::Auth::AuthFinders do
end
context 'route_setting is not correct' do
- let(:personal_access_token) { create(:personal_access_token, user: user) }
+ let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
let(:route_authentication_setting) { { basic_auth_personal_access_token: false } }
it 'returns nil' do
@@ -611,8 +628,9 @@ RSpec.describe Gitlab::Auth::AuthFinders do
context 'with CI username' do
let(:username) { ::Gitlab::Auth::CI_JOB_USER }
- let(:user) { create(:user) }
- let(:build) { create(:ci_build, user: user, status: :running) }
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:build) { create(:ci_build, user: user, status: :running) }
it 'returns nil without password' do
set_basic_auth_header(username, nil)
@@ -645,11 +663,11 @@ RSpec.describe Gitlab::Auth::AuthFinders do
describe '#validate_access_token!' do
subject { validate_access_token! }
- let(:personal_access_token) { create(:personal_access_token, user: user) }
+ let_it_be_with_reload(:personal_access_token) { create(:personal_access_token, user: user) }
context 'with a job token' do
+ let_it_be(:job) { create(:ci_build, user: user, status: :running) }
let(:route_authentication_setting) { { job_token_allowed: true } }
- let(:job) { create(:ci_build, user: user, status: :running) }
before do
env['HTTP_AUTHORIZATION'] = "Bearer #{job.token}"
@@ -671,7 +689,7 @@ RSpec.describe Gitlab::Auth::AuthFinders do
end
it 'returns Gitlab::Auth::ExpiredError if token expired' do
- personal_access_token.expires_at = 1.day.ago
+ personal_access_token.update!(expires_at: 1.day.ago)
expect { validate_access_token! }.to raise_error(Gitlab::Auth::ExpiredError)
end
@@ -688,7 +706,7 @@ RSpec.describe Gitlab::Auth::AuthFinders do
end
context 'with impersonation token' do
- let(:personal_access_token) { create(:personal_access_token, :impersonation, user: user) }
+ let_it_be(:personal_access_token) { create(:personal_access_token, :impersonation, user: user) }
context 'when impersonation is disabled' do
before do
@@ -704,7 +722,7 @@ RSpec.describe Gitlab::Auth::AuthFinders do
end
describe '#find_user_from_job_token' do
- let(:job) { create(:ci_build, user: user, status: :running) }
+ let_it_be(:job) { create(:ci_build, user: user, status: :running) }
let(:route_authentication_setting) { { job_token_allowed: true } }
subject { find_user_from_job_token }
@@ -866,7 +884,7 @@ RSpec.describe Gitlab::Auth::AuthFinders do
end
describe '#find_runner_from_token' do
- let(:runner) { create(:ci_runner) }
+ let_it_be(:runner) { create(:ci_runner) }
context 'with API requests' do
before do
diff --git a/spec/lib/gitlab/auth/current_user_mode_spec.rb b/spec/lib/gitlab/auth/current_user_mode_spec.rb
index ffd7813190a..a21f0931b78 100644
--- a/spec/lib/gitlab/auth/current_user_mode_spec.rb
+++ b/spec/lib/gitlab/auth/current_user_mode_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode, :request_store do
+RSpec.describe Gitlab::Auth::CurrentUserMode, :request_store do
let(:user) { build_stubbed(:user) }
subject { described_class.new(user) }
diff --git a/spec/lib/gitlab/auth/ldap/config_spec.rb b/spec/lib/gitlab/auth/ldap/config_spec.rb
index e4c87a54365..7a657cce597 100644
--- a/spec/lib/gitlab/auth/ldap/config_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/config_spec.rb
@@ -5,6 +5,10 @@ require 'spec_helper'
RSpec.describe Gitlab::Auth::Ldap::Config do
include LdapHelpers
+ before do
+ stub_ldap_setting(enabled: true)
+ end
+
let(:config) { described_class.new('ldapmain') }
def raw_cert
@@ -68,12 +72,28 @@ AtlErSqafbECNDSwS5BX8yDpu5yRBJ4xegO/rNlmb8ICRYkuJapD1xXicFOsmfUK
describe '.servers' do
it 'returns empty array if no server information is available' do
- allow(Gitlab.config).to receive(:ldap).and_return('enabled' => false)
+ stub_ldap_setting(servers: {})
expect(described_class.servers).to eq []
end
end
+ describe '.available_providers' do
+ before do
+ stub_licensed_features(multiple_ldap_servers: false)
+ stub_ldap_setting(
+ 'servers' => {
+ 'main' => { 'provider_name' => 'ldapmain' },
+ 'secondary' => { 'provider_name' => 'ldapsecondary' }
+ }
+ )
+ end
+
+ it 'returns one provider' do
+ expect(described_class.available_providers).to match_array(%w(ldapmain))
+ end
+ end
+
describe '#initialize' do
it 'requires a provider' do
expect { described_class.new }.to raise_error ArgumentError
diff --git a/spec/lib/gitlab/auth/request_authenticator_spec.rb b/spec/lib/gitlab/auth/request_authenticator_spec.rb
index ef6b1d72712..93e9cb06786 100644
--- a/spec/lib/gitlab/auth/request_authenticator_spec.rb
+++ b/spec/lib/gitlab/auth/request_authenticator_spec.rb
@@ -47,7 +47,10 @@ RSpec.describe Gitlab::Auth::RequestAuthenticator do
let!(:job_token_user) { build(:user) }
it 'returns access_token user first' do
- allow_any_instance_of(described_class).to receive(:find_user_from_web_access_token).and_return(access_token_user)
+ allow_any_instance_of(described_class).to receive(:find_user_from_web_access_token)
+ .with(anything, scopes: [:api, :read_api])
+ .and_return(access_token_user)
+
allow_any_instance_of(described_class).to receive(:find_user_from_feed_token).and_return(feed_token_user)
expect(subject.find_sessionless_user(:api)).to eq access_token_user
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index dfd21983682..4e4bbd1bb60 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -372,6 +372,11 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
expect(gl_auth.find_for_git_client(project_bot_user.username, project_access_token.token, project: project, ip: 'ip'))
.to eq(Gitlab::Auth::Result.new(project_bot_user, nil, :personal_access_token, described_class.full_authentication_abilities))
end
+
+ it 'successfully authenticates the project bot with a nil project' do
+ expect(gl_auth.find_for_git_client(project_bot_user.username, project_access_token.token, project: nil, ip: 'ip'))
+ .to eq(Gitlab::Auth::Result.new(project_bot_user, nil, :personal_access_token, described_class.full_authentication_abilities))
+ end
end
context 'with invalid project access token' do
diff --git a/spec/lib/gitlab/background_migration/backfill_artifact_expiry_date_spec.rb b/spec/lib/gitlab/background_migration/backfill_artifact_expiry_date_spec.rb
new file mode 100644
index 00000000000..49fa7b41916
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/backfill_artifact_expiry_date_spec.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::BackfillArtifactExpiryDate, :migration, schema: 20201111152859 do
+ subject(:perform) { migration.perform(1, 99) }
+
+ let(:migration) { described_class.new }
+ let(:artifact_outside_id_range) { create_artifact!(id: 100, created_at: 1.year.ago, expire_at: nil) }
+ let(:artifact_outside_date_range) { create_artifact!(id: 40, created_at: Time.current, expire_at: nil) }
+ let(:old_artifact) { create_artifact!(id: 10, created_at: 16.months.ago, expire_at: nil) }
+ let(:recent_artifact) { create_artifact!(id: 20, created_at: 1.year.ago, expire_at: nil) }
+ let(:artifact_with_expiry) { create_artifact!(id: 30, created_at: 1.year.ago, expire_at: Time.current + 1.day) }
+
+ before do
+ table(:namespaces).create!(id: 1, name: 'the-namespace', path: 'the-path')
+ table(:projects).create!(id: 1, name: 'the-project', namespace_id: 1)
+ table(:ci_builds).create!(id: 1, allow_failure: false)
+ end
+
+ context 'when current date is before the 22nd' do
+ before do
+ travel_to(Time.zone.local(2020, 1, 1, 0, 0, 0))
+ end
+
+ it 'backfills the expiry date for old artifacts' do
+ expect(old_artifact.reload.expire_at).to eq(nil)
+
+ perform
+
+ expect(old_artifact.reload.expire_at).to be_within(1.minute).of(Time.zone.local(2020, 4, 22, 0, 0, 0))
+ end
+
+ it 'backfills the expiry date for recent artifacts' do
+ expect(recent_artifact.reload.expire_at).to eq(nil)
+
+ perform
+
+ expect(recent_artifact.reload.expire_at).to be_within(1.minute).of(Time.zone.local(2021, 1, 22, 0, 0, 0))
+ end
+ end
+
+ context 'when current date is after the 22nd' do
+ before do
+ travel_to(Time.zone.local(2020, 1, 23, 0, 0, 0))
+ end
+
+ it 'backfills the expiry date for old artifacts' do
+ expect(old_artifact.reload.expire_at).to eq(nil)
+
+ perform
+
+ expect(old_artifact.reload.expire_at).to be_within(1.minute).of(Time.zone.local(2020, 5, 22, 0, 0, 0))
+ end
+
+ it 'backfills the expiry date for recent artifacts' do
+ expect(recent_artifact.reload.expire_at).to eq(nil)
+
+ perform
+
+ expect(recent_artifact.reload.expire_at).to be_within(1.minute).of(Time.zone.local(2021, 2, 22, 0, 0, 0))
+ end
+ end
+
+ it 'does not touch artifacts with expiry date' do
+ expect { perform }.not_to change { artifact_with_expiry.reload.expire_at }
+ end
+
+ it 'does not touch artifacts outside id range' do
+ expect { perform }.not_to change { artifact_outside_id_range.reload.expire_at }
+ end
+
+ it 'does not touch artifacts outside date range' do
+ expect { perform }.not_to change { artifact_outside_date_range.reload.expire_at }
+ end
+
+ private
+
+ def create_artifact!(**args)
+ table(:ci_job_artifacts).create!(**args, project_id: 1, job_id: 1, file_type: 1)
+ end
+end
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
new file mode 100644
index 00000000000..110a1ff8a08
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/copy_column_using_background_migration_job_spec.rb
@@ -0,0 +1,91 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::CopyColumnUsingBackgroundMigrationJob do
+ let(:table_name) { :copy_primary_key_test }
+ let(:test_table) { table(table_name) }
+ let(:sub_batch_size) { 1000 }
+
+ before do
+ ActiveRecord::Base.connection.execute(<<~SQL)
+ CREATE TABLE #{table_name}
+ (
+ id integer NOT NULL,
+ name character varying,
+ fk integer NOT NULL,
+ id_convert_to_bigint bigint DEFAULT 0 NOT NULL,
+ fk_convert_to_bigint bigint DEFAULT 0 NOT NULL,
+ name_convert_to_text text DEFAULT 'no name'
+ );
+ SQL
+
+ # Insert some data, it doesn't make a difference
+ test_table.create!(id: 11, name: 'test1', fk: 1)
+ test_table.create!(id: 12, name: 'test2', fk: 2)
+ test_table.create!(id: 15, name: nil, fk: 3)
+ test_table.create!(id: 19, name: 'test4', fk: 4)
+ end
+
+ after do
+ # Make sure that the temp table we created is dropped (it is not removed by the database_cleaner)
+ ActiveRecord::Base.connection.execute(<<~SQL)
+ DROP TABLE IF EXISTS #{table_name};
+ SQL
+ end
+
+ subject { described_class.new }
+
+ describe '#perform' do
+ let(:migration_class) { described_class.name }
+ let!(:job1) do
+ table(:background_migration_jobs).create!(
+ class_name: migration_class,
+ arguments: [1, 10, table_name, 'id', 'id', 'id_convert_to_bigint', sub_batch_size]
+ )
+ end
+
+ let!(:job2) do
+ table(:background_migration_jobs).create!(
+ class_name: migration_class,
+ arguments: [11, 20, table_name, 'id', 'id', 'id_convert_to_bigint', sub_batch_size]
+ )
+ end
+
+ it 'copies all primary keys in range' do
+ subject.perform(12, 15, table_name, 'id', 'id', 'id_convert_to_bigint', sub_batch_size)
+
+ expect(test_table.where('id = id_convert_to_bigint').pluck(:id)).to contain_exactly(12, 15)
+ expect(test_table.where(id_convert_to_bigint: 0).pluck(:id)).to contain_exactly(11, 19)
+ expect(test_table.all.count).to eq(4)
+ end
+
+ it 'copies all foreign keys in range' do
+ subject.perform(10, 14, table_name, 'id', 'fk', 'fk_convert_to_bigint', sub_batch_size)
+
+ expect(test_table.where('fk = fk_convert_to_bigint').pluck(:id)).to contain_exactly(11, 12)
+ expect(test_table.where(fk_convert_to_bigint: 0).pluck(:id)).to contain_exactly(15, 19)
+ expect(test_table.all.count).to eq(4)
+ end
+
+ it 'copies columns with NULLs' do
+ expect(test_table.where("name_convert_to_text = 'no name'").count).to eq(4)
+
+ subject.perform(10, 20, table_name, 'id', 'name', 'name_convert_to_text', sub_batch_size)
+
+ expect(test_table.where('name = name_convert_to_text').pluck(:id)).to contain_exactly(11, 12, 19)
+ 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 completion with BackgroundMigrationJob' do
+ expect do
+ subject.perform(11, 20, table_name, 'id', 'id', 'id_convert_to_bigint', sub_batch_size)
+ end.to change { Gitlab::Database::BackgroundMigrationJob.succeeded.count }.from(0).to(1)
+
+ expect(job1.reload.status).to eq(0)
+ expect(job2.reload.status).to eq(1)
+ expect(test_table.where('id = id_convert_to_bigint').count).to eq(4)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/populate_finding_uuid_for_vulnerability_feedback_spec.rb b/spec/lib/gitlab/background_migration/populate_finding_uuid_for_vulnerability_feedback_spec.rb
new file mode 100644
index 00000000000..8e74935e127
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/populate_finding_uuid_for_vulnerability_feedback_spec.rb
@@ -0,0 +1,113 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::PopulateFindingUuidForVulnerabilityFeedback, schema: 20201211090634 do
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:users) { table(:users) }
+ let(:scanners) { table(:vulnerability_scanners) }
+ let(:identifiers) { table(:vulnerability_identifiers) }
+ let(:findings) { table(:vulnerability_occurrences) }
+ let(:vulnerability_feedback) { table(:vulnerability_feedback) }
+
+ let(:namespace) { namespaces.create!(name: 'gitlab', path: 'gitlab-org') }
+ let(:project) { projects.create!(namespace_id: namespace.id, name: 'foo') }
+ let(:user) { users.create!(username: 'john.doe', projects_limit: 5) }
+ let(:scanner) { scanners.create!(project_id: project.id, external_id: 'foo', name: 'bar') }
+ let(:identifier) { identifiers.create!(project_id: project.id, fingerprint: 'foo', external_type: 'bar', external_id: 'zoo', name: 'baz') }
+ let(:sast_report) { 0 }
+ let(:dependency_scanning_report) { 1 }
+ let(:dast_report) { 3 }
+ let(:secret_detection_report) { 4 }
+ let(:project_fingerprint) { Digest::SHA1.hexdigest(SecureRandom.uuid) }
+ let(:location_fingerprint_1) { Digest::SHA1.hexdigest(SecureRandom.uuid) }
+ let(:location_fingerprint_2) { Digest::SHA1.hexdigest(SecureRandom.uuid) }
+ let(:location_fingerprint_3) { Digest::SHA1.hexdigest(SecureRandom.uuid) }
+ let(:finding_1) { finding_creator.call(sast_report, location_fingerprint_1) }
+ let(:finding_2) { finding_creator.call(dast_report, location_fingerprint_2) }
+ let(:finding_3) { finding_creator.call(secret_detection_report, location_fingerprint_3) }
+ let(:uuid_1_components) { ['sast', identifier.fingerprint, location_fingerprint_1, project.id].join('-') }
+ let(:uuid_2_components) { ['dast', identifier.fingerprint, location_fingerprint_2, project.id].join('-') }
+ let(:uuid_3_components) { ['secret_detection', identifier.fingerprint, location_fingerprint_3, project.id].join('-') }
+ let(:expected_uuid_1) { Gitlab::UUID.v5(uuid_1_components) }
+ let(:expected_uuid_2) { Gitlab::UUID.v5(uuid_2_components) }
+ let(:expected_uuid_3) { Gitlab::UUID.v5(uuid_3_components) }
+ let(:finding_creator) do
+ -> (report_type, location_fingerprint) do
+ findings.create!(
+ project_id: project.id,
+ primary_identifier_id: identifier.id,
+ scanner_id: scanner.id,
+ report_type: report_type,
+ uuid: SecureRandom.uuid,
+ name: 'Foo',
+ location_fingerprint: Gitlab::Database::ShaAttribute.serialize(location_fingerprint),
+ project_fingerprint: Gitlab::Database::ShaAttribute.serialize(project_fingerprint),
+ metadata_version: '1',
+ severity: 0,
+ confidence: 5,
+ raw_metadata: '{}'
+ )
+ end
+ end
+
+ let(:feedback_creator) do
+ -> (category, project_fingerprint) do
+ vulnerability_feedback.create!(
+ project_id: project.id,
+ author_id: user.id,
+ feedback_type: 0,
+ category: category,
+ project_fingerprint: project_fingerprint
+ )
+ end
+ end
+
+ let!(:feedback_1) { feedback_creator.call(finding_1.report_type, project_fingerprint) }
+ let!(:feedback_2) { feedback_creator.call(finding_2.report_type, project_fingerprint) }
+ let!(:feedback_3) { feedback_creator.call(finding_3.report_type, project_fingerprint) }
+ let!(:feedback_4) { feedback_creator.call(finding_1.report_type, 'foo') }
+ let!(:feedback_5) { feedback_creator.call(dependency_scanning_report, project_fingerprint) }
+
+ subject(:populate_finding_uuids) { described_class.new.perform(feedback_1.id, feedback_5.id) }
+
+ before do
+ allow(Gitlab::BackgroundMigration::Logger).to receive(:info)
+ end
+
+ describe '#perform' do
+ it 'updates the `finding_uuid` attributes of the feedback records' do
+ expect { populate_finding_uuids }.to change { feedback_1.reload.finding_uuid }.from(nil).to(expected_uuid_1)
+ .and change { feedback_2.reload.finding_uuid }.from(nil).to(expected_uuid_2)
+ .and change { feedback_3.reload.finding_uuid }.from(nil).to(expected_uuid_3)
+ .and not_change { feedback_4.reload.finding_uuid }
+ .and not_change { feedback_5.reload.finding_uuid }
+
+ expect(Gitlab::BackgroundMigration::Logger).to have_received(:info).once
+ end
+
+ it 'preloads the finding and identifier records to prevent N+1 queries' do
+ # Load feedback records(1), load findings(2), load identifiers(3) and finally update feedback records one by one(6)
+ expect { populate_finding_uuids }.not_to exceed_query_limit(6)
+ end
+
+ context 'when setting the `finding_uuid` attribute of a feedback record fails' do
+ let(:expected_error) { RuntimeError.new }
+
+ before do
+ allow(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception)
+
+ allow_next_found_instance_of(described_class::VulnerabilityFeedback) do |feedback|
+ allow(feedback).to receive(:update_column).and_raise(expected_error)
+ end
+ end
+
+ it 'captures the errors and does not crash entirely' do
+ expect { populate_finding_uuids }.not_to raise_error
+
+ expect(Gitlab::ErrorTracking).to have_received(:track_and_raise_for_dev_exception).with(expected_error).exactly(3).times
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/remove_duplicate_services_spec.rb b/spec/lib/gitlab/background_migration/remove_duplicate_services_spec.rb
new file mode 100644
index 00000000000..391b27b28e6
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/remove_duplicate_services_spec.rb
@@ -0,0 +1,121 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::RemoveDuplicateServices, :migration, schema: 20201207165956 do
+ let_it_be(:users) { table(:users) }
+ let_it_be(:namespaces) { table(:namespaces) }
+ let_it_be(:projects) { table(:projects) }
+ let_it_be(:services) { table(:services) }
+
+ let_it_be(:alerts_service_data) { table(:alerts_service_data) }
+ let_it_be(:chat_names) { table(:chat_names) }
+ let_it_be(:issue_tracker_data) { table(:issue_tracker_data) }
+ let_it_be(:jira_tracker_data) { table(:jira_tracker_data) }
+ let_it_be(:open_project_tracker_data) { table(:open_project_tracker_data) }
+ let_it_be(:slack_integrations) { table(:slack_integrations) }
+ let_it_be(:web_hooks) { table(:web_hooks) }
+
+ let_it_be(:data_tables) do
+ [alerts_service_data, chat_names, issue_tracker_data, jira_tracker_data, open_project_tracker_data, slack_integrations, web_hooks]
+ end
+
+ let!(:user) { users.create!(id: 1, projects_limit: 100) }
+ let!(:namespace) { namespaces.create!(id: 1, name: 'group', path: 'group') }
+
+ # project without duplicate services
+ let!(:project1) { projects.create!(id: 1, namespace_id: namespace.id) }
+ let!(:service1) { services.create!(id: 1, project_id: project1.id, type: 'AsanaService') }
+ let!(:service2) { services.create!(id: 2, project_id: project1.id, type: 'JiraService') }
+ let!(:service3) { services.create!(id: 3, project_id: project1.id, type: 'SlackService') }
+
+ # project with duplicate services
+ let!(:project2) { projects.create!(id: 2, namespace_id: namespace.id) }
+ let!(:service4) { services.create!(id: 4, project_id: project2.id, type: 'AsanaService') }
+ let!(:service5) { services.create!(id: 5, project_id: project2.id, type: 'JiraService') }
+ let!(:service6) { services.create!(id: 6, project_id: project2.id, type: 'JiraService') }
+ let!(:service7) { services.create!(id: 7, project_id: project2.id, type: 'SlackService') }
+ let!(:service8) { services.create!(id: 8, project_id: project2.id, type: 'SlackService') }
+ let!(:service9) { services.create!(id: 9, project_id: project2.id, type: 'SlackService') }
+
+ # project with duplicate services and dependant records
+ let!(:project3) { projects.create!(id: 3, namespace_id: namespace.id) }
+ let!(:service10) { services.create!(id: 10, project_id: project3.id, type: 'AlertsService') }
+ let!(:service11) { services.create!(id: 11, project_id: project3.id, type: 'AlertsService') }
+ let!(:service12) { services.create!(id: 12, project_id: project3.id, type: 'SlashCommandsService') }
+ let!(:service13) { services.create!(id: 13, project_id: project3.id, type: 'SlashCommandsService') }
+ let!(:service14) { services.create!(id: 14, project_id: project3.id, type: 'IssueTrackerService') }
+ let!(:service15) { services.create!(id: 15, project_id: project3.id, type: 'IssueTrackerService') }
+ let!(:service16) { services.create!(id: 16, project_id: project3.id, type: 'JiraService') }
+ let!(:service17) { services.create!(id: 17, project_id: project3.id, type: 'JiraService') }
+ let!(:service18) { services.create!(id: 18, project_id: project3.id, type: 'OpenProjectService') }
+ let!(:service19) { services.create!(id: 19, project_id: project3.id, type: 'OpenProjectService') }
+ let!(:service20) { services.create!(id: 20, project_id: project3.id, type: 'SlackService') }
+ let!(:service21) { services.create!(id: 21, project_id: project3.id, type: 'SlackService') }
+ let!(:dependant_records) do
+ alerts_service_data.create!(id: 1, service_id: service10.id)
+ alerts_service_data.create!(id: 2, service_id: service11.id)
+ chat_names.create!(id: 1, service_id: service12.id, user_id: user.id, team_id: 'team1', chat_id: 'chat1')
+ chat_names.create!(id: 2, service_id: service13.id, user_id: user.id, team_id: 'team2', chat_id: 'chat2')
+ issue_tracker_data.create!(id: 1, service_id: service14.id)
+ issue_tracker_data.create!(id: 2, service_id: service15.id)
+ jira_tracker_data.create!(id: 1, service_id: service16.id)
+ jira_tracker_data.create!(id: 2, service_id: service17.id)
+ open_project_tracker_data.create!(id: 1, service_id: service18.id)
+ open_project_tracker_data.create!(id: 2, service_id: service19.id)
+ slack_integrations.create!(id: 1, service_id: service20.id, user_id: user.id, team_id: 'team1', team_name: 'team1', alias: 'alias1')
+ slack_integrations.create!(id: 2, service_id: service21.id, user_id: user.id, team_id: 'team2', team_name: 'team2', alias: 'alias2')
+ web_hooks.create!(id: 1, service_id: service20.id)
+ web_hooks.create!(id: 2, service_id: service21.id)
+ end
+
+ # project without services
+ let!(:project4) { projects.create!(id: 4, namespace_id: namespace.id) }
+
+ it 'removes duplicate services and dependant records' do
+ # Determine which services we expect to keep
+ expected_services = projects.pluck(:id).each_with_object({}) do |project_id, map|
+ project_services = services.where(project_id: project_id)
+ types = project_services.distinct.pluck(:type)
+
+ map[project_id] = types.map { |type| project_services.where(type: type).take!.id }
+ end
+
+ expect do
+ subject.perform(project2.id, project3.id)
+ end.to change { services.count }.from(21).to(12)
+
+ services1 = services.where(project_id: project1.id)
+ expect(services1.count).to be(3)
+ expect(services1.pluck(:type)).to contain_exactly('AsanaService', 'JiraService', 'SlackService')
+ expect(services1.pluck(:id)).to contain_exactly(*expected_services[project1.id])
+
+ services2 = services.where(project_id: project2.id)
+ expect(services2.count).to be(3)
+ expect(services2.pluck(:type)).to contain_exactly('AsanaService', 'JiraService', 'SlackService')
+ expect(services2.pluck(:id)).to contain_exactly(*expected_services[project2.id])
+
+ services3 = services.where(project_id: project3.id)
+ expect(services3.count).to be(6)
+ expect(services3.pluck(:type)).to contain_exactly('AlertsService', 'SlashCommandsService', 'IssueTrackerService', 'JiraService', 'OpenProjectService', 'SlackService')
+ expect(services3.pluck(:id)).to contain_exactly(*expected_services[project3.id])
+
+ kept_services = expected_services.values.flatten
+ data_tables.each do |table|
+ expect(table.count).to be(1)
+ expect(kept_services).to include(table.pluck(:service_id).first)
+ end
+ end
+
+ it 'does not delete services without duplicates' do
+ expect do
+ subject.perform(project1.id, project4.id)
+ end.not_to change { services.count }
+ end
+
+ it 'only deletes duplicate services for the current batch' do
+ expect do
+ subject.perform(project2.id)
+ end.to change { services.count }.by(-3)
+ end
+end
diff --git a/spec/lib/gitlab/checks/diff_check_spec.rb b/spec/lib/gitlab/checks/diff_check_spec.rb
index f4daafb1d0e..6b45b8d4628 100644
--- a/spec/lib/gitlab/checks/diff_check_spec.rb
+++ b/spec/lib/gitlab/checks/diff_check_spec.rb
@@ -6,96 +6,63 @@ RSpec.describe Gitlab::Checks::DiffCheck do
include_context 'change access checks context'
describe '#validate!' do
- let(:owner) { create(:user) }
-
- before do
- allow(project.repository).to receive(:new_commits).and_return(
- project.repository.commits_between('be93687618e4b132087f430a4d8fc3a609c9b77c', '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51')
- )
- end
-
- context 'with LFS not enabled' do
- before do
- allow(project).to receive(:lfs_enabled?).and_return(false)
- end
-
- it 'does not invoke :lfs_file_locks_validation' do
- expect(subject).not_to receive(:lfs_file_locks_validation)
+ context 'when commits is empty' do
+ it 'does not call find_changed_paths' do
+ expect(project.repository).not_to receive(:find_changed_paths)
subject.validate!
end
end
- context 'with LFS enabled' do
- let!(:lock) { create(:lfs_file_lock, user: owner, project: project, path: 'README') }
-
+ context 'when commits is not empty' do
before do
- allow(project).to receive(:lfs_enabled?).and_return(true)
+ allow(project.repository).to receive(:new_commits).and_return(
+ project.repository.commits_between('be93687618e4b132087f430a4d8fc3a609c9b77c', '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51')
+ )
end
- context 'when change is sent by a different user' do
- context 'when diff check with paths rpc feature flag is true' do
- it 'raises an error if the user is not allowed to update the file' do
- expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, "The path 'README' is locked in Git LFS by #{lock.user.name}")
- end
- end
+ context 'when deletion is true' do
+ let(:newrev) { Gitlab::Git::BLANK_SHA }
- context 'when diff check with paths rpc feature flag is false' do
- before do
- stub_feature_flags(diff_check_with_paths_changed_rpc: false)
- end
+ it 'does not call find_changed_paths' do
+ expect(project.repository).not_to receive(:find_changed_paths)
- it 'raises an error if the user is not allowed to update the file' do
- expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, "The path 'README' is locked in Git LFS by #{lock.user.name}")
- end
+ subject.validate!
end
end
- context 'when change is sent by the author of the lock' do
- let(:user) { owner }
-
- it "doesn't raise any error" do
- expect { subject.validate! }.not_to raise_error
+ context 'with LFS not enabled' do
+ before do
+ allow(project).to receive(:lfs_enabled?).and_return(false)
end
- end
- end
-
- context 'commit diff validations' do
- before do
- allow(subject).to receive(:validations_for_diff).and_return([lambda { |diff| return }])
-
- expect_any_instance_of(Commit).to receive(:raw_deltas).and_call_original
-
- stub_feature_flags(diff_check_with_paths_changed_rpc: false)
-
- subject.validate!
- end
- context 'when request store is inactive' do
- it 'are run for every commit' do
- expect_any_instance_of(Commit).to receive(:raw_deltas).and_call_original
+ it 'does not invoke :lfs_file_locks_validation' do
+ expect(subject).not_to receive(:lfs_file_locks_validation)
subject.validate!
end
end
- context 'when request store is active', :request_store do
- it 'are cached for every commit' do
- expect_any_instance_of(Commit).not_to receive(:raw_deltas)
+ context 'with LFS enabled' do
+ let(:owner) { create(:user) }
+ let!(:lock) { create(:lfs_file_lock, user: owner, project: project, path: 'README') }
- subject.validate!
+ before do
+ allow(project).to receive(:lfs_enabled?).and_return(true)
end
- it 'are run for not cached commits' do
- allow(project.repository).to receive(:new_commits).and_return(
- project.repository.commits_between('be93687618e4b132087f430a4d8fc3a609c9b77c', 'a5391128b0ef5d21df5dd23d98557f4ef12fae20')
- )
- change_access.instance_variable_set(:@commits, project.repository.new_commits)
+ context 'when change is sent by a different user' do
+ it 'raises an error if the user is not allowed to update the file' do
+ expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, "The path 'README' is locked in Git LFS by #{lock.user.name}")
+ end
+ end
- expect(project.repository.new_commits.first).not_to receive(:raw_deltas).and_call_original
- expect(project.repository.new_commits.last).to receive(:raw_deltas).and_call_original
+ context 'when change is sent by the author of the lock' do
+ let(:user) { owner }
- subject.validate!
+ it "doesn't raise any error" do
+ expect { subject.validate! }.not_to raise_error
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/artifacts_spec.rb b/spec/lib/gitlab/ci/config/entry/artifacts_spec.rb
index 028dcd3e1e6..0e6d5b6c311 100644
--- a/spec/lib/gitlab/ci/config/entry/artifacts_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/artifacts_spec.rb
@@ -36,6 +36,14 @@ RSpec.describe Gitlab::Ci::Config::Entry::Artifacts do
expect(entry.value).to eq config
end
end
+
+ context "when value includes 'public' keyword" do
+ let(:config) { { paths: %w[results.txt], public: false } }
+
+ it 'returns general artifact and report-type artifacts configuration' do
+ expect(entry.value).to eq config
+ end
+ end
end
context 'when entry value is not correct' do
@@ -67,6 +75,15 @@ RSpec.describe Gitlab::Ci::Config::Entry::Artifacts do
end
end
+ context "when 'public' is not a boolean" do
+ let(:config) { { paths: %w[results.txt], public: 'false' } }
+
+ it 'reports error' do
+ expect(entry.errors)
+ .to include 'artifacts public should be a boolean value'
+ end
+ end
+
context "when 'expose_as' is not a string" do
let(:config) { { paths: %w[results.txt], expose_as: 1 } }
diff --git a/spec/lib/gitlab/ci/config/entry/variables_spec.rb b/spec/lib/gitlab/ci/config/entry/variables_spec.rb
index 426a38e2ef7..78d37e228df 100644
--- a/spec/lib/gitlab/ci/config/entry/variables_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/variables_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Config::Entry::Variables do
let(:metadata) { {} }
- subject { described_class.new(config, metadata) }
+ subject { described_class.new(config, **metadata) }
shared_examples 'valid config' do
describe '#value' do
diff --git a/spec/lib/gitlab/ci/config/external/file/local_spec.rb b/spec/lib/gitlab/ci/config/external/file/local_spec.rb
index fdd29afe2d6..7e39fae7b9b 100644
--- a/spec/lib/gitlab/ci/config/external/file/local_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/local_spec.rb
@@ -16,7 +16,8 @@ RSpec.describe Gitlab::Ci::Config::External::File::Local do
project: project,
sha: sha,
user: user,
- parent_pipeline: parent_pipeline
+ parent_pipeline: parent_pipeline,
+ variables: project.predefined_variables.to_runner_variables
}
end
@@ -131,7 +132,8 @@ RSpec.describe Gitlab::Ci::Config::External::File::Local do
user: user,
project: project,
sha: sha,
- parent_pipeline: parent_pipeline)
+ parent_pipeline: parent_pipeline,
+ variables: project.predefined_variables.to_runner_variables)
end
end
diff --git a/spec/lib/gitlab/ci/config/external/file/project_spec.rb b/spec/lib/gitlab/ci/config/external/file/project_spec.rb
index a5e4e27df6f..0e8851ba915 100644
--- a/spec/lib/gitlab/ci/config/external/file/project_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/project_spec.rb
@@ -16,7 +16,8 @@ RSpec.describe Gitlab::Ci::Config::External::File::Project do
project: context_project,
sha: '12345',
user: context_user,
- parent_pipeline: parent_pipeline
+ parent_pipeline: parent_pipeline,
+ variables: project.predefined_variables.to_runner_variables
}
end
@@ -165,7 +166,8 @@ RSpec.describe Gitlab::Ci::Config::External::File::Project do
user: user,
project: project,
sha: project.commit('master').id,
- parent_pipeline: parent_pipeline)
+ parent_pipeline: parent_pipeline,
+ variables: project.predefined_variables.to_runner_variables)
end
end
diff --git a/spec/lib/gitlab/ci/config/external/mapper_spec.rb b/spec/lib/gitlab/ci/config/external/mapper_spec.rb
index 7ad57827e30..4fdaaca8316 100644
--- a/spec/lib/gitlab/ci/config/external/mapper_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/mapper_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper do
let(:local_file) { '/lib/gitlab/ci/templates/non-existent-file.yml' }
let(:remote_url) { 'https://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.gitlab-ci-1.yml' }
let(:template_file) { 'Auto-DevOps.gitlab-ci.yml' }
- let(:context_params) { { project: project, sha: '123456', user: user } }
+ let(:context_params) { { project: project, sha: '123456', user: user, variables: project.predefined_variables.to_runner_variables } }
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
let(:file_content) do
@@ -124,17 +124,6 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper do
an_instance_of(Gitlab::Ci::Config::External::File::Project),
an_instance_of(Gitlab::Ci::Config::External::File::Project))
end
-
- context 'when FF ci_include_multiple_files_from_project is disabled' do
- before do
- stub_feature_flags(ci_include_multiple_files_from_project: false)
- end
-
- it 'returns a File instance' do
- expect(subject).to contain_exactly(
- an_instance_of(Gitlab::Ci::Config::External::File::Project))
- end
- end
end
end
@@ -236,5 +225,118 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper do
end
end
end
+
+ context "when 'include' section uses project variable" do
+ let(:full_local_file_path) { '$CI_PROJECT_PATH' + local_file }
+
+ context 'when local file is included as a single string' do
+ let(:values) do
+ { include: full_local_file_path }
+ end
+
+ it 'expands the variable', :aggregate_failures do
+ expect(subject[0].location).to eq(project.full_path + local_file)
+ expect(subject).to contain_exactly(an_instance_of(Gitlab::Ci::Config::External::File::Local))
+ end
+ end
+
+ context 'when remote file is included as a single string' do
+ let(:remote_url) { "#{Gitlab.config.gitlab.url}/radio/.gitlab-ci.yml" }
+
+ let(:values) do
+ { include: '$CI_SERVER_URL/radio/.gitlab-ci.yml' }
+ end
+
+ it 'expands the variable', :aggregate_failures do
+ expect(subject[0].location).to eq(remote_url)
+ expect(subject).to contain_exactly(an_instance_of(Gitlab::Ci::Config::External::File::Remote))
+ end
+ end
+
+ context 'defined as an array' do
+ let(:values) do
+ { include: [full_local_file_path, remote_url],
+ image: 'ruby:2.7' }
+ end
+
+ it 'expands the variable' do
+ expect(subject[0].location).to eq(project.full_path + local_file)
+ expect(subject[1].location).to eq(remote_url)
+ end
+ end
+
+ context 'defined as an array of hashes' do
+ let(:values) do
+ { include: [{ local: full_local_file_path }, { remote: remote_url }],
+ image: 'ruby:2.7' }
+ end
+
+ it 'expands the variable' do
+ expect(subject[0].location).to eq(project.full_path + local_file)
+ expect(subject[1].location).to eq(remote_url)
+ end
+ end
+
+ context 'local file hash' do
+ let(:values) do
+ { include: { 'local' => full_local_file_path } }
+ end
+
+ it 'expands the variable' do
+ expect(subject[0].location).to eq(project.full_path + local_file)
+ end
+ end
+
+ context 'project name' do
+ let(:values) do
+ { include: { project: '$CI_PROJECT_PATH', file: local_file },
+ image: 'ruby:2.7' }
+ end
+
+ it 'expands the variable', :aggregate_failures do
+ expect(subject[0].project_name).to eq(project.full_path)
+ expect(subject).to contain_exactly(an_instance_of(Gitlab::Ci::Config::External::File::Project))
+ end
+ end
+
+ context 'with multiple files' do
+ let(:values) do
+ { include: { project: project.full_path, file: [full_local_file_path, 'another_file_path.yml'] },
+ image: 'ruby:2.7' }
+ end
+
+ it 'expands the variable' do
+ expect(subject[0].location).to eq(project.full_path + local_file)
+ expect(subject[1].location).to eq('another_file_path.yml')
+ end
+ end
+
+ context 'when include variable has an unsupported type for variable expansion' do
+ let(:values) do
+ { include: { project: project.id, file: local_file },
+ image: 'ruby:2.7' }
+ end
+
+ it 'does not invoke expansion for the variable', :aggregate_failures do
+ expect(ExpandVariables).not_to receive(:expand).with(project.id, context_params[:variables])
+
+ expect { subject }.to raise_error(described_class::AmbigiousSpecificationError)
+ end
+ end
+
+ context 'when feature flag is turned off' do
+ let(:values) do
+ { include: full_local_file_path }
+ end
+
+ before do
+ stub_feature_flags(variables_in_include_section_ci: false)
+ end
+
+ it 'does not expand the variables' do
+ expect(subject[0].location).to eq('$CI_PROJECT_PATH' + local_file)
+ 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 150a2ec2929..d2d7116bb12 100644
--- a/spec/lib/gitlab/ci/config/external/processor_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/processor_spec.rb
@@ -365,19 +365,6 @@ RSpec.describe Gitlab::Ci::Config::External::Processor do
output = processor.perform
expect(output.keys).to match_array([:image, :my_build, :my_test])
end
-
- context 'when FF ci_include_multiple_files_from_project is disabled' do
- before do
- stub_feature_flags(ci_include_multiple_files_from_project: false)
- end
-
- it 'raises an error' do
- expect { processor.perform }.to raise_error(
- described_class::IncludeError,
- 'Included file `["/templates/my-build.yml", "/templates/my-test.yml"]` needs to be a string'
- )
- end
- end
end
end
end
diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb
index b5a0f0e3fd7..dc03d2f80fe 100644
--- a/spec/lib/gitlab/ci/config_spec.rb
+++ b/spec/lib/gitlab/ci/config_spec.rb
@@ -82,6 +82,30 @@ RSpec.describe Gitlab::Ci::Config do
end
end
+ describe '#included_templates' do
+ let(:yml) do
+ <<-EOS
+ include:
+ - template: Jobs/Deploy.gitlab-ci.yml
+ - template: Jobs/Build.gitlab-ci.yml
+ - remote: https://example.com/gitlab-ci.yml
+ EOS
+ end
+
+ before do
+ stub_request(:get, 'https://example.com/gitlab-ci.yml').to_return(status: 200, body: <<-EOS)
+ test:
+ script: [ 'echo hello world' ]
+ EOS
+ end
+
+ subject(:included_templates) do
+ config.included_templates
+ end
+
+ it { is_expected.to contain_exactly('Jobs/Deploy.gitlab-ci.yml', 'Jobs/Build.gitlab-ci.yml') }
+ end
+
context 'when using extendable hash' do
let(:yml) do
<<-EOS
diff --git a/spec/lib/gitlab/ci/lint_spec.rb b/spec/lib/gitlab/ci/lint_spec.rb
index c67f8464123..67324c09d86 100644
--- a/spec/lib/gitlab/ci/lint_spec.rb
+++ b/spec/lib/gitlab/ci/lint_spec.rb
@@ -247,7 +247,7 @@ RSpec.describe Gitlab::Ci::Lint do
include_context 'advanced validations' do
it 'runs advanced logical validations' do
expect(subject).not_to be_valid
- expect(subject.errors).to eq(["test: needs 'build'"])
+ expect(subject.errors).to eq(["'test' job needs 'build' job, but it was not added to the pipeline"])
end
end
diff --git a/spec/lib/gitlab/ci/parsers/coverage/cobertura_spec.rb b/spec/lib/gitlab/ci/parsers/coverage/cobertura_spec.rb
index 2313378d1e9..546de2bee5c 100644
--- a/spec/lib/gitlab/ci/parsers/coverage/cobertura_spec.rb
+++ b/spec/lib/gitlab/ci/parsers/coverage/cobertura_spec.rb
@@ -224,6 +224,12 @@ RSpec.describe Gitlab::Ci::Parsers::Coverage::Cobertura do
it_behaves_like 'ignoring sources, project_path, and worktree_paths'
end
+ context 'when there is an empty <sources>' do
+ let(:sources_xml) { '<sources />' }
+
+ it_behaves_like 'ignoring sources, project_path, and worktree_paths'
+ end
+
context 'when there is a <sources>' do
context 'and has a single source with a pattern for Go projects' do
let(:project_path) { 'local/go' } # Make sure we're not making false positives
diff --git a/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb
index 6da565a2bf6..20406acb658 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Gitlab::Ci::Pipeline::Chain::Build do
- let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:project, reload: true) { create(:project, :repository) }
let_it_be(:user) { create(:user, developer_projects: [project]) }
let(:pipeline) { Ci::Pipeline.new }
@@ -29,29 +29,96 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Build do
let(:step) { described_class.new(pipeline, command) }
- before do
- stub_ci_pipeline_yaml_file(gitlab_ci_yaml)
+ shared_examples 'builds pipeline' do
+ it 'builds a pipeline with the expected attributes' do
+ step.perform!
+
+ expect(pipeline.sha).not_to be_empty
+ expect(pipeline.sha).to eq project.commit.id
+ expect(pipeline.ref).to eq 'master'
+ expect(pipeline.tag).to be false
+ expect(pipeline.user).to eq user
+ expect(pipeline.project).to eq project
+ end
end
- it 'never breaks the chain' do
- step.perform!
+ shared_examples 'breaks the chain' do
+ it 'returns true' do
+ step.perform!
+
+ expect(step.break?).to be true
+ end
+ end
+
+ shared_examples 'does not break the chain' do
+ it 'returns false' do
+ step.perform!
+
+ expect(step.break?).to be false
+ end
+ end
- expect(step.break?).to be false
+ before do
+ stub_ci_pipeline_yaml_file(gitlab_ci_yaml)
end
- it 'fills pipeline object with data' do
+ it_behaves_like 'does not break the chain'
+ it_behaves_like 'builds pipeline'
+
+ it 'sets pipeline variables' do
step.perform!
- expect(pipeline.sha).not_to be_empty
- expect(pipeline.sha).to eq project.commit.id
- expect(pipeline.ref).to eq 'master'
- expect(pipeline.tag).to be false
- expect(pipeline.user).to eq user
- expect(pipeline.project).to eq project
expect(pipeline.variables.map { |var| var.slice(:key, :secret_value) })
.to eq variables_attributes.map(&:with_indifferent_access)
end
+ context 'when project setting restrict_user_defined_variables is enabled' do
+ before do
+ project.update!(restrict_user_defined_variables: true)
+ end
+
+ context 'when user is developer' do
+ it_behaves_like 'breaks the chain'
+ it_behaves_like 'builds pipeline'
+
+ it 'returns an error on variables_attributes', :aggregate_failures do
+ step.perform!
+
+ expect(pipeline.errors.full_messages).to eq(['Insufficient permissions to set pipeline variables'])
+ expect(pipeline.variables).to be_empty
+ end
+
+ context 'when variables_attributes is not specified' do
+ let(:variables_attributes) { nil }
+
+ it_behaves_like 'does not break the chain'
+ it_behaves_like 'builds pipeline'
+
+ it 'assigns empty variables' do
+ step.perform!
+
+ expect(pipeline.variables).to be_empty
+ end
+ end
+ end
+
+ context 'when user is maintainer' do
+ before do
+ project.add_maintainer(user)
+ end
+
+ it_behaves_like 'does not break the chain'
+ it_behaves_like 'builds pipeline'
+
+ it 'assigns variables_attributes' do
+ step.perform!
+
+ expect(pipeline.variables.map { |var| var.slice(:key, :secret_value) })
+ .to eq variables_attributes.map(&:with_indifferent_access)
+ end
+ end
+ end
+
it 'returns a valid pipeline' do
step.perform!
@@ -157,4 +224,25 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Build do
expect(pipeline.target_sha).to eq(external_pull_request.target_sha)
end
end
+
+ context 'when keep_latest_artifact is set' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:keep_latest_artifact, :locking_result) do
+ true | 'artifacts_locked'
+ false | 'unlocked'
+ end
+
+ with_them do
+ before do
+ project.update!(ci_keep_latest_artifact: keep_latest_artifact)
+ end
+
+ it 'builds a pipeline with appropriate locked value' do
+ step.perform!
+
+ expect(pipeline.locked).to eq(locking_result)
+ end
+ 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 bc2012e83bd..9ca5aeeea58 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
@@ -295,4 +295,30 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Command do
it { is_expected.to eq(false) }
end
end
+
+ describe '#creates_child_pipeline?' do
+ let(:command) { described_class.new(bridge: bridge) }
+
+ subject { command.creates_child_pipeline? }
+
+ context 'when bridge is present' do
+ context 'when bridge triggers a child pipeline' do
+ let(:bridge) { double(:bridge, triggers_child_pipeline?: true) }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when bridge triggers a multi-project pipeline' do
+ let(:bridge) { double(:bridge, triggers_child_pipeline?: false) }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ context 'when bridge is not present' do
+ let(:bridge) { nil }
+
+ it { is_expected.to be_falsey }
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/seed_block_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/seed_block_spec.rb
index 85c8e20767f..fabfbd779f3 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/seed_block_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/seed_block_spec.rb
@@ -51,18 +51,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::SeedBlock do
expect(pipeline.variables.size).to eq(1)
end
-
- context 'when FF ci_seed_block_run_before_workflow_rules is disabled' do
- before do
- stub_feature_flags(ci_seed_block_run_before_workflow_rules: false)
- end
-
- it 'does not execute the block' do
- run_chain
-
- expect(pipeline.variables.size).to eq(0)
- end
- end
end
context 'when the seeds_block tries to save the pipelie' do
diff --git a/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb
index 0ce8b80902e..80013cab6ee 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb
@@ -20,6 +20,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
describe '#perform!' do
before do
stub_ci_pipeline_yaml_file(YAML.dump(config))
+ run_chain
end
let(:config) do
@@ -36,20 +37,14 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
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
@@ -64,8 +59,6 @@ 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
@@ -91,8 +84,6 @@ 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
@@ -112,8 +103,6 @@ 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
@@ -141,8 +130,6 @@ 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
@@ -154,8 +141,6 @@ 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
@@ -173,8 +158,6 @@ 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
@@ -187,24 +170,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
->(pipeline) { pipeline.variables.build(key: 'VAR', value: '123') }
end
- context 'when FF ci_seed_block_run_before_workflow_rules is enabled' do
- it 'does not execute the block' do
- run_chain
-
- expect(pipeline.variables.size).to eq(0)
- end
- end
-
- context 'when FF ci_seed_block_run_before_workflow_rules is disabled' do
- before do
- stub_feature_flags(ci_seed_block_run_before_workflow_rules: false)
- end
-
- it 'executes the block' do
- run_chain
-
- expect(pipeline.variables.size).to eq(1)
- end
+ it 'does not execute the block' do
+ expect(pipeline.variables.size).to eq(0)
end
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/template_usage_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/template_usage_spec.rb
new file mode 100644
index 00000000000..3616461d94f
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/chain/template_usage_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Pipeline::Chain::TemplateUsage do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:user) { create(:user) }
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+
+ let(: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 'tracks the included templates' do
+ expect(command).to(
+ receive(:yaml_processor_result)
+ .and_return(
+ double(included_templates: %w(Template-1 Template-2))
+ )
+ )
+
+ %w(Template-1 Template-2).each do |expected_template|
+ expect(Gitlab::UsageDataCounters::CiTemplateUniqueCounter).to(
+ receive(:track_unique_project_event)
+ .with(project_id: project.id, template: expected_template)
+ )
+ end
+
+ perform
+ 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 bc10e94c81d..cf020fc343c 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
@@ -966,7 +966,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
it "returns an error" do
expect(subject.errors).to contain_exactly(
- "rspec: needs 'build'")
+ "'rspec' job needs 'build' job, but it was not added to the pipeline")
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/seed/pipeline_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/pipeline_spec.rb
index 1790388da03..860b07647bd 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/pipeline_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/pipeline_spec.rb
@@ -62,7 +62,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Pipeline do
needs_attributes: [{ name: 'non-existent', artifacts: true }]
}
- expect(seed.errors).to contain_exactly("invalid_job: needs 'non-existent'")
+ expect(seed.errors).to contain_exactly(
+ "'invalid_job' job needs 'non-existent' job, but it was not added to the pipeline")
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 8df34eddffd..831bc5e9f37 100644
--- a/spec/lib/gitlab/ci/reports/test_failure_history_spec.rb
+++ b/spec/lib/gitlab/ci/reports/test_failure_history_spec.rb
@@ -28,18 +28,5 @@ RSpec.describe Gitlab::Ci::Reports::TestFailureHistory, :aggregate_failures do
expect(failed_rspec.recent_failures).to eq(count: 2, base_branch: 'master')
expect(failed_java.recent_failures).to eq(count: 1, base_branch: 'master')
end
-
- context 'when feature flag is disabled' do
- before do
- stub_feature_flags(test_failure_history: false)
- end
-
- it 'does not set recent failures' do
- load_history
-
- expect(failed_rspec.recent_failures).to be_nil
- expect(failed_java.recent_failures).to be_nil
- end
- end
end
end
diff --git a/spec/lib/gitlab/ci/status/group/factory_spec.rb b/spec/lib/gitlab/ci/status/group/factory_spec.rb
index 6267b26aa78..c67c7ff8271 100644
--- a/spec/lib/gitlab/ci/status/group/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/group/factory_spec.rb
@@ -12,4 +12,9 @@ RSpec.describe Gitlab::Ci::Status::Group::Factory do
expect(described_class.common_helpers)
.to eq Gitlab::Ci::Status::Group::Common
end
+
+ it 'exposes extended statuses' do
+ expect(described_class.extended_statuses)
+ .to eq([[Gitlab::Ci::Status::SuccessWarning]])
+ end
end
diff --git a/spec/lib/gitlab/ci/syntax_templates_spec.rb b/spec/lib/gitlab/ci/syntax_templates_spec.rb
new file mode 100644
index 00000000000..ce3169e17ec
--- /dev/null
+++ b/spec/lib/gitlab/ci/syntax_templates_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'ci/syntax_templates' do
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { create(:user) }
+ let(:lint) { Gitlab::Ci::Lint.new(project: project, current_user: user) }
+
+ before do
+ project.add_developer(user)
+ end
+
+ subject(:lint_result) { lint.validate(content) }
+
+ Dir.glob('lib/gitlab/ci/syntax_templates/**/*.yml').each do |template|
+ describe template do
+ let(:content) { File.read(template) }
+
+ it 'validates the template' do
+ expect(lint_result).to be_valid, "got errors: #{lint_result.errors.join(', ')}"
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/templates/5_minute_production_app_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/5_minute_production_app_ci_yaml_spec.rb
new file mode 100644
index 00000000000..6bc8e261640
--- /dev/null
+++ b/spec/lib/gitlab/ci/templates/5_minute_production_app_ci_yaml_spec.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe '5-Minute-Production-App.gitlab-ci.yml' do
+ subject(:template) { Gitlab::Template::GitlabCiYmlTemplate.find('5-Minute-Production-App') }
+
+ describe 'the created pipeline' do
+ let_it_be(:project) { create(:project, :auto_devops, :custom_repo, files: { 'README.md' => '' }) }
+
+ let(:user) { project.owner }
+ let(:default_branch) { 'master' }
+ let(:pipeline_branch) { default_branch }
+ let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch ) }
+ let(:pipeline) { service.execute!(:push) }
+ let(:build_names) { pipeline.builds.pluck(:name) }
+
+ before do
+ stub_ci_pipeline_yaml_file(template.content)
+ end
+
+ it 'creates only build job' do
+ expect(build_names).to match_array('build')
+ end
+
+ context 'when AWS variables are set' do
+ before do
+ create(:ci_variable, project: project, key: 'AWS_ACCESS_KEY_ID', value: 'AKIAIOSFODNN7EXAMPLE')
+ create(:ci_variable, project: project, key: 'AWS_SECRET_ACCESS_KEY', value: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY')
+ create(:ci_variable, project: project, key: 'AWS_DEFAULT_REGION', value: 'us-west-2')
+ end
+
+ it 'creates all jobs' do
+ expect(build_names).to match_array(%w(build terraform_apply deploy terraform_destroy))
+ end
+
+ context 'pipeline branch is protected' do
+ before do
+ create(:protected_branch, project: project, name: pipeline_branch)
+ project.reload
+ end
+
+ it 'does not create a destroy job' do
+ expect(build_names).to match_array(%w(build terraform_apply deploy))
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/templates/AWS/deploy_ecs_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/AWS/deploy_ecs_gitlab_ci_yaml_spec.rb
index 4be92e8608e..653b3be0b2a 100644
--- a/spec/lib/gitlab/ci/templates/AWS/deploy_ecs_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/AWS/deploy_ecs_gitlab_ci_yaml_spec.rb
@@ -6,10 +6,10 @@ RSpec.describe 'Deploy-ECS.gitlab-ci.yml' do
subject(:template) { Gitlab::Template::GitlabCiYmlTemplate.find('AWS/Deploy-ECS') }
describe 'the created pipeline' do
- let_it_be(:user) { create(:admin) }
let(:default_branch) { 'master' }
let(:pipeline_branch) { default_branch }
let(:project) { create(:project, :auto_devops, :custom_repo, files: { 'README.md' => '' }) }
+ let(:user) { project.owner }
let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch ) }
let(:pipeline) { service.execute!(:push) }
let(:build_names) { pipeline.builds.pluck(:name) }
diff --git a/spec/lib/gitlab/ci/templates/Jobs/build_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Jobs/build_gitlab_ci_yaml_spec.rb
index 4f8faa5ddb1..1f278048ad5 100644
--- a/spec/lib/gitlab/ci/templates/Jobs/build_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/Jobs/build_gitlab_ci_yaml_spec.rb
@@ -6,8 +6,8 @@ RSpec.describe 'Jobs/Build.gitlab-ci.yml' do
subject(:template) { Gitlab::Template::GitlabCiYmlTemplate.find('Jobs/Build') }
describe 'the created pipeline' do
- let_it_be(:user) { create(:admin) }
let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { project.owner }
let(:default_branch) { 'master' }
let(:pipeline_ref) { default_branch }
diff --git a/spec/lib/gitlab/ci/templates/Jobs/code_quality_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Jobs/code_quality_gitlab_ci_yaml_spec.rb
index e685ad3b46e..0a76de82421 100644
--- a/spec/lib/gitlab/ci/templates/Jobs/code_quality_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/Jobs/code_quality_gitlab_ci_yaml_spec.rb
@@ -6,8 +6,8 @@ RSpec.describe 'Jobs/Code-Quality.gitlab-ci.yml' do
subject(:template) { Gitlab::Template::GitlabCiYmlTemplate.find('Jobs/Code-Quality') }
describe 'the created pipeline' do
- let_it_be(:user) { create(:admin) }
let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { project.owner }
let(:default_branch) { 'master' }
let(:pipeline_ref) { default_branch }
diff --git a/spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb
index ea9bd5bd02c..25c88c161ea 100644
--- a/spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb
@@ -27,8 +27,8 @@ RSpec.describe 'Jobs/Deploy.gitlab-ci.yml' do
end
describe 'the created pipeline' do
- let(:user) { create(:admin) }
let(:project) { create(:project, :repository) }
+ let(:user) { project.owner }
let(:default_branch) { 'master' }
let(:pipeline_ref) { default_branch }
diff --git a/spec/lib/gitlab/ci/templates/Jobs/test_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Jobs/test_gitlab_ci_yaml_spec.rb
index f475785be98..b64959a9917 100644
--- a/spec/lib/gitlab/ci/templates/Jobs/test_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/Jobs/test_gitlab_ci_yaml_spec.rb
@@ -6,8 +6,8 @@ RSpec.describe 'Jobs/Test.gitlab-ci.yml' do
subject(:template) { Gitlab::Template::GitlabCiYmlTemplate.find('Jobs/Test') }
describe 'the created pipeline' do
- let_it_be(:user) { create(:admin) }
let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { project.owner }
let(:default_branch) { 'master' }
let(:pipeline_ref) { default_branch }
diff --git a/spec/lib/gitlab/ci/templates/Terraform/base_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Terraform/base_gitlab_ci_yaml_spec.rb
index 8df739d9245..0811c07e896 100644
--- a/spec/lib/gitlab/ci/templates/Terraform/base_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/Terraform/base_gitlab_ci_yaml_spec.rb
@@ -6,10 +6,10 @@ RSpec.describe 'Terraform/Base.latest.gitlab-ci.yml' do
subject(:template) { Gitlab::Template::GitlabCiYmlTemplate.find('Terraform/Base.latest') }
describe 'the created pipeline' do
- let(:user) { create(:admin) }
let(:default_branch) { 'master' }
let(:pipeline_branch) { default_branch }
let(:project) { create(:project, :custom_repo, files: { 'README.md' => '' }) }
+ let(:user) { project.owner }
let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch ) }
let(:pipeline) { service.execute!(:push) }
let(:build_names) { pipeline.builds.pluck(:name) }
diff --git a/spec/lib/gitlab/ci/templates/Verify/load_performance_testing_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Verify/load_performance_testing_gitlab_ci_yaml_spec.rb
index 9711df55226..03fa45fe0a1 100644
--- a/spec/lib/gitlab/ci/templates/Verify/load_performance_testing_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/Verify/load_performance_testing_gitlab_ci_yaml_spec.rb
@@ -19,8 +19,8 @@ RSpec.describe 'Verify/Load-Performance-Testing.gitlab-ci.yml' do
end
describe 'the created pipeline' do
- let(:user) { create(:admin) }
let(:project) { create(:project, :repository) }
+ let(:user) { project.owner }
let(:default_branch) { 'master' }
let(:pipeline_ref) { default_branch }
diff --git a/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb
index 793df55f45d..f9d6fe24e70 100644
--- a/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb
@@ -6,10 +6,10 @@ RSpec.describe 'Auto-DevOps.gitlab-ci.yml' do
subject(:template) { Gitlab::Template::GitlabCiYmlTemplate.find('Auto-DevOps') }
describe 'the created pipeline' do
- let(:user) { create(:admin) }
let(:default_branch) { 'master' }
let(:pipeline_branch) { default_branch }
let(:project) { create(:project, :auto_devops, :custom_repo, files: { 'README.md' => '' }) }
+ let(:user) { project.owner }
let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch ) }
let(:pipeline) { service.execute!(:push) }
let(:build_names) { pipeline.builds.pluck(:name) }
@@ -232,8 +232,8 @@ RSpec.describe 'Auto-DevOps.gitlab-ci.yml' do
end
with_them do
- let(:user) { create(:admin) }
let(:project) { create(:project, :custom_repo, files: files) }
+ let(:user) { project.owner }
let(:service) { Ci::CreatePipelineService.new(project, user, ref: 'master' ) }
let(:pipeline) { service.execute(:push) }
let(:build_names) { pipeline.builds.pluck(:name) }
diff --git a/spec/lib/gitlab/ci/templates/flutter_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/flutter_gitlab_ci_yaml_spec.rb
new file mode 100644
index 00000000000..4e5fe622648
--- /dev/null
+++ b/spec/lib/gitlab/ci/templates/flutter_gitlab_ci_yaml_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Flutter.gitlab-ci.yml' do
+ subject(:template) { Gitlab::Template::GitlabCiYmlTemplate.find('Flutter') }
+
+ describe 'the created pipeline' do
+ let(:pipeline_branch) { 'master' }
+ let(:project) { create(:project, :custom_repo, files: { 'README.md' => '' }) }
+ let(:user) { project.owner }
+ let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch ) }
+ let(:pipeline) { service.execute!(:push) }
+ let(:build_names) { pipeline.builds.pluck(:name) }
+
+ before do
+ stub_ci_pipeline_yaml_file(template.content)
+ allow(Ci::BuildScheduleWorker).to receive(:perform).and_return(true)
+ end
+
+ it 'creates test and code_quality jobs' do
+ expect(build_names).to include('test', 'code_quality')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/templates/npm_spec.rb b/spec/lib/gitlab/ci/templates/npm_spec.rb
index 1f8e32ce019..b10e2b0e057 100644
--- a/spec/lib/gitlab/ci/templates/npm_spec.rb
+++ b/spec/lib/gitlab/ci/templates/npm_spec.rb
@@ -6,11 +6,10 @@ RSpec.describe 'npm.latest.gitlab-ci.yml' do
subject(:template) { Gitlab::Template::GitlabCiYmlTemplate.find('npm.latest') }
describe 'the created pipeline' do
- let_it_be(:user) { create(:admin) }
-
let(:repo_files) { { 'package.json' => '{}', 'README.md' => '' } }
let(:modified_files) { %w[package.json] }
let(:project) { create(:project, :custom_repo, files: repo_files) }
+ let(:user) { project.owner }
let(:pipeline_branch) { project.default_branch }
let(:pipeline_tag) { 'v1.2.1' }
let(:pipeline_ref) { pipeline_branch }
diff --git a/spec/lib/gitlab/ci/templates/terraform_latest_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/terraform_latest_gitlab_ci_yaml_spec.rb
index 5eec021b9d7..4377f155d34 100644
--- a/spec/lib/gitlab/ci/templates/terraform_latest_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/terraform_latest_gitlab_ci_yaml_spec.rb
@@ -10,11 +10,10 @@ RSpec.describe 'Terraform.latest.gitlab-ci.yml' do
subject(:template) { Gitlab::Template::GitlabCiYmlTemplate.find('Terraform.latest') }
describe 'the created pipeline' do
- let_it_be(:user) { create(:admin) }
-
let(:default_branch) { 'master' }
let(:pipeline_branch) { default_branch }
let(:project) { create(:project, :custom_repo, files: { 'README.md' => '' }) }
+ let(:user) { project.owner }
let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch ) }
let(:pipeline) { service.execute!(:push) }
let(:build_names) { pipeline.builds.pluck(:name) }
diff --git a/spec/lib/gitlab/ci/variables/collection/sorted_spec.rb b/spec/lib/gitlab/ci/variables/collection/sorted_spec.rb
new file mode 100644
index 00000000000..d85bf29f77f
--- /dev/null
+++ b/spec/lib/gitlab/ci/variables/collection/sorted_spec.rb
@@ -0,0 +1,251 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Variables::Collection::Sorted do
+ describe '#errors' do
+ context 'when FF :variable_inside_variable is disabled' do
+ before do
+ stub_feature_flags(variable_inside_variable: false)
+ end
+
+ context 'table tests' do
+ using RSpec::Parameterized::TableSyntax
+
+ where do
+ {
+ "empty array": {
+ variables: []
+ },
+ "simple expansions": {
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'result' },
+ { key: 'variable3', value: 'key$variable$variable2' }
+ ]
+ },
+ "complex expansion": {
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'key${variable}' }
+ ]
+ },
+ "complex expansions with missing variable for Windows": {
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable3', value: 'key%variable%%variable2%' }
+ ]
+ },
+ "out-of-order variable reference": {
+ variables: [
+ { key: 'variable2', value: 'key${variable}' },
+ { key: 'variable', value: 'value' }
+ ]
+ },
+ "array with cyclic dependency": {
+ variables: [
+ { key: 'variable', value: '$variable2' },
+ { key: 'variable2', value: '$variable3' },
+ { key: 'variable3', value: 'key$variable$variable2' }
+ ]
+ }
+ }
+ end
+
+ with_them do
+ subject { Gitlab::Ci::Variables::Collection::Sorted.new(variables) }
+
+ it 'does not report error' do
+ expect(subject.errors).to eq(nil)
+ end
+
+ it 'valid? reports true' do
+ expect(subject.valid?).to eq(true)
+ end
+ end
+ end
+ end
+
+ context 'when FF :variable_inside_variable is enabled' do
+ before do
+ stub_feature_flags(variable_inside_variable: true)
+ end
+
+ context 'table tests' do
+ using RSpec::Parameterized::TableSyntax
+
+ where do
+ {
+ "empty array": {
+ variables: [],
+ validation_result: nil
+ },
+ "simple expansions": {
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'result' },
+ { key: 'variable3', value: 'key$variable$variable2' }
+ ],
+ validation_result: nil
+ },
+ "cyclic dependency": {
+ variables: [
+ { key: 'variable', value: '$variable2' },
+ { key: 'variable2', value: '$variable3' },
+ { key: 'variable3', value: 'key$variable$variable2' }
+ ],
+ validation_result: 'circular variable reference detected: ["variable", "variable2", "variable3"]'
+ }
+ }
+ end
+
+ with_them do
+ subject { Gitlab::Ci::Variables::Collection::Sorted.new(variables) }
+
+ it 'errors matches expected validation result' do
+ expect(subject.errors).to eq(validation_result)
+ end
+
+ it 'valid? matches expected validation result' do
+ expect(subject.valid?).to eq(validation_result.nil?)
+ end
+ end
+ end
+ end
+ end
+
+ describe '#sort' do
+ context 'when FF :variable_inside_variable is disabled' do
+ before do
+ stub_feature_flags(variable_inside_variable: false)
+ end
+
+ context 'table tests' do
+ using RSpec::Parameterized::TableSyntax
+
+ where do
+ {
+ "empty array": {
+ variables: []
+ },
+ "simple expansions": {
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'result' },
+ { key: 'variable3', value: 'key$variable$variable2' }
+ ]
+ },
+ "complex expansion": {
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'key${variable}' }
+ ]
+ },
+ "complex expansions with missing variable for Windows": {
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable3', value: 'key%variable%%variable2%' }
+ ]
+ },
+ "out-of-order variable reference": {
+ variables: [
+ { key: 'variable2', value: 'key${variable}' },
+ { key: 'variable', value: 'value' }
+ ]
+ },
+ "array with cyclic dependency": {
+ variables: [
+ { key: 'variable', value: '$variable2' },
+ { key: 'variable2', value: '$variable3' },
+ { key: 'variable3', value: 'key$variable$variable2' }
+ ]
+ }
+ }
+ end
+
+ with_them do
+ subject { Gitlab::Ci::Variables::Collection::Sorted.new(variables) }
+
+ it 'does not expand variables' do
+ expect(subject.sort).to eq(variables)
+ end
+ end
+ end
+ end
+
+ context 'when FF :variable_inside_variable is enabled' do
+ before do
+ stub_licensed_features(group_saml_group_sync: true)
+ stub_feature_flags(saml_group_links: true)
+ stub_feature_flags(variable_inside_variable: true)
+ end
+
+ context 'table tests' do
+ using RSpec::Parameterized::TableSyntax
+
+ where do
+ {
+ "empty array": {
+ variables: [],
+ result: []
+ },
+ "simple expansions, no reordering needed": {
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'result' },
+ { key: 'variable3', value: 'key$variable$variable2' }
+ ],
+ result: %w[variable variable2 variable3]
+ },
+ "complex expansion, reordering needed": {
+ variables: [
+ { key: 'variable2', value: 'key${variable}' },
+ { key: 'variable', value: 'value' }
+ ],
+ result: %w[variable variable2]
+ },
+ "unused variables": {
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable4', value: 'key$variable$variable3' },
+ { key: 'variable2', value: 'result2' },
+ { key: 'variable3', value: 'result3' }
+ ],
+ result: %w[variable variable3 variable4 variable2]
+ },
+ "missing variable": {
+ variables: [
+ { key: 'variable2', value: 'key$variable' }
+ ],
+ result: %w[variable2]
+ },
+ "complex expansions with missing variable": {
+ variables: [
+ { key: 'variable4', value: 'key${variable}${variable2}${variable3}' },
+ { key: 'variable', value: 'value' },
+ { key: 'variable3', value: 'value3' }
+ ],
+ result: %w[variable variable3 variable4]
+ },
+ "cyclic dependency causes original array to be returned": {
+ variables: [
+ { key: 'variable2', value: '$variable3' },
+ { key: 'variable3', value: 'key$variable$variable2' },
+ { key: 'variable', value: '$variable2' }
+ ],
+ result: %w[variable2 variable3 variable]
+ }
+ }
+ end
+
+ with_them do
+ subject { Gitlab::Ci::Variables::Collection::Sorted.new(variables) }
+
+ it 'sort returns correctly sorted variables' do
+ expect(subject.sort.map { |var| var[:key] }).to eq(result)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb
index 5ad1b3dd241..9498453852a 100644
--- a/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -2711,40 +2711,6 @@ module Gitlab
end
end
- describe "#validation_message" do
- subject { Gitlab::Ci::YamlProcessor.validation_message(content) }
-
- context "when the YAML could not be parsed" do
- let(:content) { YAML.dump("invalid: yaml: test") }
-
- it { is_expected.to eq "Invalid configuration format" }
- end
-
- context "when the tags parameter is invalid" do
- let(:content) { YAML.dump({ rspec: { script: "test", tags: "mysql" } }) }
-
- it { is_expected.to eq "jobs:rspec:tags config should be an array of strings" }
- end
-
- context "when YAML content is empty" do
- let(:content) { '' }
-
- it { is_expected.to eq "Please provide content of .gitlab-ci.yml" }
- end
-
- context 'when the YAML contains an unknown alias' do
- let(:content) { 'steps: *bad_alias' }
-
- it { is_expected.to eq "Unknown alias: bad_alias" }
- end
-
- context "when the YAML is valid" do
- let(:content) { File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) }
-
- it { is_expected.to be_nil }
- end
- end
-
describe '#execute' do
subject { Gitlab::Ci::YamlProcessor.new(content).execute }
diff --git a/spec/lib/gitlab/composer/version_index_spec.rb b/spec/lib/gitlab/composer/version_index_spec.rb
new file mode 100644
index 00000000000..4c4742d9f59
--- /dev/null
+++ b/spec/lib/gitlab/composer/version_index_spec.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Composer::VersionIndex do
+ let_it_be(:package_name) { 'sample-project' }
+ let_it_be(:json) { { 'name' => package_name } }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project, :custom_repo, files: { 'composer.json' => json.to_json }, group: group) }
+ let_it_be(:package1) { create(:composer_package, :with_metadatum, project: project, name: package_name, version: '1.0.0', json: json) }
+ let_it_be(:package2) { create(:composer_package, :with_metadatum, project: project, name: package_name, version: '2.0.0', json: json) }
+
+ let(:branch) { project.repository.find_branch('master') }
+
+ let(:packages) { [package1, package2] }
+
+ describe '#as_json' do
+ subject(:index) { described_class.new(packages).as_json }
+
+ def expected_json(package)
+ {
+ 'dist' => {
+ 'reference' => branch.target,
+ 'shasum' => '',
+ 'type' => 'zip',
+ 'url' => "http://localhost/api/v4/projects/#{project.id}/packages/composer/archives/#{package.name}.zip?sha=#{branch.target}"
+ },
+ 'name' => package.name,
+ 'uid' => package.id,
+ 'version' => package.version
+ }
+ end
+
+ it 'returns the packages json' do
+ packages = index['packages'][package_name]
+
+ expect(packages['1.0.0']).to eq(expected_json(package1))
+ expect(packages['2.0.0']).to eq(expected_json(package2))
+ end
+ end
+
+ describe '#sha' do
+ subject(:sha) { described_class.new(packages).sha }
+
+ it 'returns the json SHA' do
+ expect(sha).to match /^[A-Fa-f0-9]{64}$/
+ end
+ end
+end
diff --git a/spec/lib/gitlab/config/entry/composable_hash_spec.rb b/spec/lib/gitlab/config/entry/composable_hash_spec.rb
index 15bbf2047c5..f64b39231a3 100644
--- a/spec/lib/gitlab/config/entry/composable_hash_spec.rb
+++ b/spec/lib/gitlab/config/entry/composable_hash_spec.rb
@@ -92,7 +92,7 @@ RSpec.describe Gitlab::Config::Entry::ComposableHash, :aggregate_failures do
end
let(:entry) do
- parent_entry = composable_hash_parent_class.new(secrets: config)
+ parent_entry = composable_hash_parent_class.new({ secrets: config })
parent_entry.compose!
parent_entry[:secrets]
diff --git a/spec/lib/gitlab/conflict/file_spec.rb b/spec/lib/gitlab/conflict/file_spec.rb
index 0de944d3f8a..bb9bee763d8 100644
--- a/spec/lib/gitlab/conflict/file_spec.rb
+++ b/spec/lib/gitlab/conflict/file_spec.rb
@@ -97,19 +97,27 @@ RSpec.describe Gitlab::Conflict::File do
let(:diff_line_types) { conflict_file.diff_lines_for_serializer.map(&:type) }
it 'assigns conflict types to the diff lines' do
- expect(diff_line_types[4]).to eq('conflict_marker')
- expect(diff_line_types[5..10]).to eq(['conflict_marker_our'] * 6)
+ expect(diff_line_types[4]).to eq('conflict_marker_our')
+ expect(diff_line_types[5..10]).to eq(['conflict_our'] * 6)
expect(diff_line_types[11]).to eq('conflict_marker')
- expect(diff_line_types[12..17]).to eq(['conflict_marker_their'] * 6)
- expect(diff_line_types[18]).to eq('conflict_marker')
+ expect(diff_line_types[12..17]).to eq(['conflict_their'] * 6)
+ expect(diff_line_types[18]).to eq('conflict_marker_their')
expect(diff_line_types[19..24]).to eq([nil] * 6)
- expect(diff_line_types[25]).to eq('conflict_marker')
- expect(diff_line_types[26..27]).to eq(['conflict_marker_our'] * 2)
+ expect(diff_line_types[25]).to eq('conflict_marker_our')
+ expect(diff_line_types[26..27]).to eq(['conflict_our'] * 2)
expect(diff_line_types[28]).to eq('conflict_marker')
- expect(diff_line_types[29..30]).to eq(['conflict_marker_their'] * 2)
- expect(diff_line_types[31]).to eq('conflict_marker')
+ expect(diff_line_types[29..30]).to eq(['conflict_their'] * 2)
+ expect(diff_line_types[31]).to eq('conflict_marker_their')
+ end
+
+ # Swap the positions around due to conflicts/diffs display inconsistency
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/291989
+ it 'swaps the new and old positions around' do
+ lines = conflict_file.diff_lines_for_serializer
+ expect(lines.map(&:old_pos)[26..27]).to eq([21, 22])
+ expect(lines.map(&:new_pos)[29..30]).to eq([21, 22])
end
it 'does not add a match line to the end of the section' do
@@ -124,13 +132,13 @@ RSpec.describe Gitlab::Conflict::File do
expect(diff_line_types).to eq([
'match',
nil, nil, nil,
- "conflict_marker",
"conflict_marker_our",
+ "conflict_our",
"conflict_marker",
+ "conflict_their",
+ "conflict_their",
+ "conflict_their",
"conflict_marker_their",
- "conflict_marker_their",
- "conflict_marker_their",
- "conflict_marker",
nil, nil, nil,
"match"
])
diff --git a/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb
deleted file mode 100644
index 056c1b5bc9f..00000000000
--- a/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::CycleAnalytics::BaseEventFetcher do
- let(:max_events) { 2 }
- let(:project) { create(:project, :repository) }
- let(:user) { create(:user, :admin) }
- let(:start_time_attrs) { Issue.arel_table[:created_at] }
- let(:end_time_attrs) { [Issue::Metrics.arel_table[:first_associated_with_milestone_at]] }
- let(:options) do
- { start_time_attrs: start_time_attrs,
- end_time_attrs: end_time_attrs,
- from: 30.days.ago,
- project: project }
- end
-
- subject do
- described_class.new(stage: :issue,
- options: options).fetch
- end
-
- before do
- allow_any_instance_of(Gitlab::ReferenceExtractor).to receive(:issues).and_return(Issue.all)
- allow_any_instance_of(described_class).to receive(:serialize) do |event|
- event
- end
- allow_any_instance_of(described_class)
- .to receive(:allowed_ids).and_return(nil)
-
- stub_const('Gitlab::CycleAnalytics::BaseEventFetcher::MAX_EVENTS', max_events)
-
- setup_events(count: 3)
- end
-
- it 'limits the rows to the max number' do
- expect(subject.count).to eq(max_events)
- end
-
- def setup_events(count:)
- count.times do
- issue = create(:issue, project: project, created_at: 2.days.ago)
- milestone = create(:milestone, project: project)
-
- issue.update(milestone: milestone)
- create_merge_request_closing_issue(user, project, issue)
- end
- end
-end
diff --git a/spec/lib/gitlab/cycle_analytics/code_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/code_event_fetcher_spec.rb
deleted file mode 100644
index a1a173abe57..00000000000
--- a/spec/lib/gitlab/cycle_analytics/code_event_fetcher_spec.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::CycleAnalytics::CodeEventFetcher do
- let(:stage_name) { :code }
-
- it_behaves_like 'default query config' do
- it 'has a default order' do
- expect(event.order).not_to be_nil
- end
- end
-end
diff --git a/spec/lib/gitlab/cycle_analytics/code_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/code_stage_spec.rb
deleted file mode 100644
index 17104715580..00000000000
--- a/spec/lib/gitlab/cycle_analytics/code_stage_spec.rb
+++ /dev/null
@@ -1,129 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::CycleAnalytics::CodeStage do
- let(:stage_name) { :code }
-
- let(:project) { create(:project) }
- let(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) }
- let(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) }
- let(:issue_3) { create(:issue, project: project, created_at: 60.minutes.ago) }
- let(:mr_1) { create(:merge_request, source_project: project, created_at: 15.minutes.ago) }
- let(:mr_2) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'A') }
- let(:stage_options) { { from: 2.days.ago, current_user: project.creator, project: project } }
- let(:stage) { described_class.new(options: stage_options) }
-
- before do
- issue_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago, first_mentioned_in_commit_at: 45.minutes.ago)
- issue_2.metrics.update!(first_added_to_board_at: 60.minutes.ago, first_mentioned_in_commit_at: 40.minutes.ago)
- issue_3.metrics.update!(first_added_to_board_at: 60.minutes.ago, first_mentioned_in_commit_at: 40.minutes.ago)
- create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'B')
- create(:merge_requests_closing_issues, merge_request: mr_1, issue: issue_1)
- create(:merge_requests_closing_issues, merge_request: mr_2, issue: issue_2)
- end
-
- it_behaves_like 'base stage'
-
- context 'when using the new query backend' do
- include_examples 'Gitlab::Analytics::CycleAnalytics::DataCollector backend examples' do
- let(:expected_record_count) { 2 }
- let(:expected_ordered_attribute_values) { [mr_2.title, mr_1.title] }
- end
- end
-
- describe '#project_median' do
- around do |example|
- freeze_time { example.run }
- end
-
- it 'counts median from issues with metrics' do
- expect(stage.project_median).to eq(ISSUES_MEDIAN)
- end
-
- include_examples 'calculate #median with date range'
- end
-
- describe '#events' do
- subject { stage.events }
-
- it 'exposes merge requests that closes issues' do
- expect(subject.count).to eq(2)
- expect(subject.map { |event| event[:title] }).to contain_exactly(mr_1.title, mr_2.title)
- end
- end
-
- context 'when group is given' do
- let(:user) { create(:user) }
- let(:group) { create(:group) }
- let(:project_2) { create(:project, group: group) }
- let(:project_3) { create(:project, group: group) }
- let(:issue_2_1) { create(:issue, project: project_2, created_at: 90.minutes.ago) }
- let(:issue_2_2) { create(:issue, project: project_3, created_at: 60.minutes.ago) }
- let(:issue_2_3) { create(:issue, project: project_2, created_at: 60.minutes.ago) }
- let(:mr_2_1) { create(:merge_request, source_project: project_2, created_at: 15.minutes.ago) }
- let(:mr_2_2) { create(:merge_request, source_project: project_3, created_at: 10.minutes.ago, source_branch: 'A') }
- let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: user, group: group }) }
-
- before do
- group.add_owner(user)
- issue_2_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago, first_mentioned_in_commit_at: 45.minutes.ago)
- issue_2_2.metrics.update!(first_added_to_board_at: 60.minutes.ago, first_mentioned_in_commit_at: 40.minutes.ago)
- issue_2_3.metrics.update!(first_added_to_board_at: 60.minutes.ago, first_mentioned_in_commit_at: 40.minutes.ago)
- create(:merge_requests_closing_issues, merge_request: mr_2_1, issue: issue_2_1)
- create(:merge_requests_closing_issues, merge_request: mr_2_2, issue: issue_2_2)
- end
-
- describe '#group_median' do
- around do |example|
- freeze_time { example.run }
- end
-
- it 'counts median from issues with metrics' do
- expect(stage.group_median).to eq(ISSUES_MEDIAN)
- end
- end
-
- describe '#events' do
- subject { stage.events }
-
- it 'exposes merge requests that close issues' do
- expect(subject.count).to eq(2)
- expect(subject.map { |event| event[:title] }).to contain_exactly(mr_2_1.title, mr_2_2.title)
- end
- end
-
- context 'when subgroup is given' do
- let(:subgroup) { create(:group, parent: group) }
- let(:project_4) { create(:project, group: subgroup) }
- let(:project_5) { create(:project, group: subgroup) }
- let(:issue_3_1) { create(:issue, project: project_4, created_at: 90.minutes.ago) }
- let(:issue_3_2) { create(:issue, project: project_5, created_at: 60.minutes.ago) }
- let(:issue_3_3) { create(:issue, project: project_5, created_at: 60.minutes.ago) }
- let(:mr_3_1) { create(:merge_request, source_project: project_4, created_at: 15.minutes.ago) }
- let(:mr_3_2) { create(:merge_request, source_project: project_5, created_at: 10.minutes.ago, source_branch: 'A') }
-
- before do
- issue_3_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago, first_mentioned_in_commit_at: 45.minutes.ago)
- issue_3_2.metrics.update!(first_added_to_board_at: 60.minutes.ago, first_mentioned_in_commit_at: 40.minutes.ago)
- issue_3_3.metrics.update!(first_added_to_board_at: 60.minutes.ago, first_mentioned_in_commit_at: 40.minutes.ago)
- create(:merge_requests_closing_issues, merge_request: mr_3_1, issue: issue_3_1)
- create(:merge_requests_closing_issues, merge_request: mr_3_2, issue: issue_3_2)
- end
-
- describe '#events' do
- subject { stage.events }
-
- it 'exposes merge requests that close issues' do
- expect(subject.count).to eq(4)
- expect(subject.map { |event| event[:title] }).to contain_exactly(mr_2_1.title, mr_2_2.title, mr_3_1.title, mr_3_2.title)
- end
-
- it 'exposes merge requests that close issues with full path for subgroup' do
- expect(subject.count).to eq(4)
- expect(subject.find { |event| event[:title] == mr_3_1.title }[:url]).to include("#{subgroup.full_path}")
- end
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/cycle_analytics/events_spec.rb b/spec/lib/gitlab/cycle_analytics/events_spec.rb
deleted file mode 100644
index 553f33a66c4..00000000000
--- a/spec/lib/gitlab/cycle_analytics/events_spec.rb
+++ /dev/null
@@ -1,182 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe 'value stream analytics events', :aggregate_failures do
- let_it_be(:project) { create(:project, :repository) }
- let_it_be(:user) { create(:user, :admin) }
- let(:from_date) { 10.days.ago }
- let!(:context) { create(:issue, project: project, created_at: 2.days.ago) }
-
- let(:events) do
- CycleAnalytics::ProjectLevel
- .new(project, options: { from: from_date, current_user: user })[stage]
- .events
- end
-
- let(:event) { events.first }
-
- before do
- setup(context)
- end
-
- describe '#issue_events' do
- let(:stage) { :issue }
-
- it 'has correct attributes' do
- expect(event[:total_time]).not_to be_empty
- expect(event[:title]).to eq(context.title)
- expect(event[:url]).not_to be_nil
- expect(event[:iid]).to eq(context.iid.to_s)
- expect(event[:created_at]).to end_with('ago')
- expect(event[:author][:web_url]).not_to be_nil
- expect(event[:author][:avatar_url]).not_to be_nil
- expect(event[:author][:name]).to eq(context.author.name)
- end
- end
-
- describe '#plan_events' do
- let(:stage) { :plan }
-
- before do
- create_commit_referencing_issue(context)
-
- # Adding extra duration because the new VSA backend filters out 0 durations between these columns
- context.metrics.update!(first_mentioned_in_commit_at: context.metrics.first_associated_with_milestone_at + 1.day)
- end
-
- it 'has correct attributes' do
- expect(event[:total_time]).not_to be_empty
- expect(event[:title]).to eq(context.title)
- expect(event[:url]).not_to be_nil
- expect(event[:iid]).to eq(context.iid.to_s)
- expect(event[:created_at]).to end_with('ago')
- expect(event[:author][:web_url]).not_to be_nil
- expect(event[:author][:avatar_url]).not_to be_nil
- expect(event[:author][:name]).to eq(context.author.name)
- end
- end
-
- describe '#code_events' do
- let(:stage) { :code }
- let!(:merge_request) { MergeRequest.first }
-
- before do
- create_commit_referencing_issue(context)
- end
-
- it 'has correct attributes' do
- expect(event[:total_time]).not_to be_empty
- expect(event[:title]).to eq('Awesome merge_request')
- expect(event[:iid]).to eq(context.iid.to_s)
- expect(event[:created_at]).to end_with('ago')
- expect(event[:author][:web_url]).not_to be_nil
- expect(event[:author][:avatar_url]).not_to be_nil
- expect(event[:author][:name]).to eq(MergeRequest.first.author.name)
- end
- end
-
- describe '#test_events', :sidekiq_might_not_need_inline do
- let(:stage) { :test }
-
- let(:merge_request) { MergeRequest.first }
- let!(:context) { create(:issue, project: project, created_at: 2.days.ago) }
-
- let!(:pipeline) do
- create(:ci_pipeline,
- ref: merge_request.source_branch,
- sha: merge_request.diff_head_sha,
- project: project,
- head_pipeline_of: merge_request)
- end
-
- before do
- create(:ci_build, :success, pipeline: pipeline, author: user)
- create(:ci_build, :success, pipeline: pipeline, author: user)
-
- pipeline.run!
- pipeline.succeed!
- merge_merge_requests_closing_issue(user, project, context)
- end
-
- it 'has correct attributes' do
- expect(event[:name]).not_to be_nil
- expect(event[:id]).not_to be_nil
- expect(event[:url]).not_to be_nil
- expect(event[:branch]).not_to be_nil
- expect(event[:branch][:url]).not_to be_nil
- expect(event[:short_sha]).not_to be_nil
- expect(event[:commit_url]).not_to be_nil
- expect(event[:date]).not_to be_nil
- expect(event[:total_time]).not_to be_empty
- end
- end
-
- describe '#review_events' do
- let(:stage) { :review }
- let!(:context) { create(:issue, project: project, created_at: 2.days.ago) }
-
- before do
- merge_merge_requests_closing_issue(user, project, context)
- end
-
- it 'has correct attributes' do
- expect(event[:total_time]).not_to be_empty
- expect(event[:title]).to eq('Awesome merge_request')
- expect(event[:iid]).to eq(context.iid.to_s)
- expect(event[:url]).not_to be_nil
- expect(event[:state]).not_to be_nil
- expect(event[:created_at]).not_to be_nil
- expect(event[:author][:web_url]).not_to be_nil
- expect(event[:author][:avatar_url]).not_to be_nil
- expect(event[:author][:name]).to eq(MergeRequest.first.author.name)
- end
- end
-
- describe '#staging_events', :sidekiq_might_not_need_inline do
- let(:stage) { :staging }
- let(:merge_request) { MergeRequest.first }
-
- let!(:pipeline) do
- create(:ci_pipeline,
- ref: merge_request.source_branch,
- sha: merge_request.diff_head_sha,
- project: project,
- head_pipeline_of: merge_request)
- end
-
- before do
- create(:ci_build, :success, pipeline: pipeline, author: user)
- create(:ci_build, :success, pipeline: pipeline, author: user)
-
- pipeline.run!
- pipeline.succeed!
-
- merge_merge_requests_closing_issue(user, project, context)
- deploy_master(user, project)
- end
-
- it 'has correct attributes' do
- expect(event[:name]).not_to be_nil
- expect(event[:id]).not_to be_nil
- expect(event[:url]).not_to be_nil
- expect(event[:branch]).not_to be_nil
- expect(event[:branch][:url]).not_to be_nil
- expect(event[:short_sha]).not_to be_nil
- expect(event[:commit_url]).not_to be_nil
- expect(event[:date]).not_to be_nil
- expect(event[:total_time]).not_to be_empty
- expect(event[:author][:web_url]).not_to be_nil
- expect(event[:author][:avatar_url]).not_to be_nil
- expect(event[:author][:name]).to eq(MergeRequest.first.author.name)
- end
- end
-
- def setup(context)
- milestone = create(:milestone, project: project)
- context.update!(milestone: milestone)
- mr = create_merge_request_closing_issue(user, project, context, commit_message: "References #{context.to_reference}")
-
- ProcessCommitWorker.new.perform(project.id, user.id, mr.commits.last.to_hash)
- end
-end
diff --git a/spec/lib/gitlab/cycle_analytics/issue_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/issue_event_fetcher_spec.rb
deleted file mode 100644
index 7a49ee53e8f..00000000000
--- a/spec/lib/gitlab/cycle_analytics/issue_event_fetcher_spec.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::CycleAnalytics::IssueEventFetcher do
- let(:stage_name) { :issue }
-
- it_behaves_like 'default query config'
-end
diff --git a/spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb
deleted file mode 100644
index c7ab2b9b84b..00000000000
--- a/spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb
+++ /dev/null
@@ -1,136 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::CycleAnalytics::IssueStage do
- let(:stage_name) { :issue }
- let(:project) { create(:project) }
- let(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) }
- let(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) }
- let(:issue_3) { create(:issue, project: project, created_at: 30.minutes.ago) }
- let!(:issue_without_milestone) { create(:issue, project: project, created_at: 1.minute.ago) }
- let(:stage_options) { { from: 2.days.ago, current_user: project.creator, project: project } }
- let(:stage) { described_class.new(options: stage_options) }
-
- before do
- issue_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago )
- issue_2.metrics.update!(first_added_to_board_at: 30.minutes.ago)
- issue_3.metrics.update!(first_added_to_board_at: 15.minutes.ago)
- end
-
- it_behaves_like 'base stage'
-
- context 'when using the new query backend' do
- include_examples 'Gitlab::Analytics::CycleAnalytics::DataCollector backend examples' do
- let(:expected_record_count) { 3 }
- let(:expected_ordered_attribute_values) { [issue_3.title, issue_2.title, issue_1.title] }
- end
- end
-
- describe '#median' do
- around do |example|
- freeze_time { example.run }
- end
-
- it 'counts median from issues with metrics' do
- expect(stage.project_median).to eq(ISSUES_MEDIAN)
- end
-
- include_examples 'calculate #median with date range'
- end
-
- describe '#events' do
- it 'exposes issues with metrics' do
- result = stage.events
-
- expect(result.count).to eq(3)
- expect(result.map { |event| event[:title] }).to contain_exactly(issue_1.title, issue_2.title, issue_3.title)
- end
- end
- context 'when group is given' do
- let(:user) { create(:user) }
- let(:group) { create(:group) }
- let(:project_2) { create(:project, group: group) }
- let(:project_3) { create(:project, group: group) }
- let(:issue_2_1) { create(:issue, project: project_2, created_at: 90.minutes.ago) }
- let(:issue_2_2) { create(:issue, project: project_3, created_at: 60.minutes.ago) }
- let(:issue_2_3) { create(:issue, project: project_2, created_at: 60.minutes.ago) }
- let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: user, group: group }) }
-
- before do
- group.add_owner(user)
- issue_2_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago)
- issue_2_2.metrics.update!(first_added_to_board_at: 30.minutes.ago)
- end
-
- describe '#group_median' do
- around do |example|
- freeze_time { example.run }
- end
-
- it 'counts median from issues with metrics' do
- expect(stage.group_median).to eq(ISSUES_MEDIAN)
- end
- end
-
- describe '#events' do
- subject { stage.events }
-
- it 'exposes merge requests that close issues' do
- expect(subject.count).to eq(2)
- expect(subject.map { |event| event[:title] }).to contain_exactly(issue_2_1.title, issue_2_2.title)
- end
- end
-
- context 'when only part of projects is chosen' do
- let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: user, group: group, projects: [project_2.id] }) }
-
- describe '#group_median' do
- around do |example|
- freeze_time { example.run }
- end
-
- it 'counts median from issues with metrics' do
- expect(stage.group_median).to eq(ISSUES_MEDIAN)
- end
- end
-
- describe '#events' do
- subject { stage.events }
-
- it 'exposes merge requests that close issues' do
- expect(subject.count).to eq(1)
- expect(subject.map { |event| event[:title] }).to contain_exactly(issue_2_1.title)
- end
- end
- end
-
- context 'when subgroup is given' do
- let(:subgroup) { create(:group, parent: group) }
- let(:project_4) { create(:project, group: subgroup) }
- let(:project_5) { create(:project, group: subgroup) }
- let(:issue_3_1) { create(:issue, project: project_4, created_at: 90.minutes.ago) }
- let(:issue_3_2) { create(:issue, project: project_5, created_at: 60.minutes.ago) }
- let(:issue_3_3) { create(:issue, project: project_5, created_at: 60.minutes.ago) }
-
- before do
- issue_3_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago)
- issue_3_2.metrics.update!(first_added_to_board_at: 30.minutes.ago)
- end
-
- describe '#events' do
- subject { stage.events }
-
- it 'exposes merge requests that close issues' do
- expect(subject.count).to eq(4)
- expect(subject.map { |event| event[:title] }).to contain_exactly(issue_2_1.title, issue_2_2.title, issue_3_1.title, issue_3_2.title)
- end
-
- it 'exposes merge requests that close issues with full path for subgroup' do
- expect(subject.count).to eq(4)
- expect(subject.find { |event| event[:title] == issue_3_1.title }[:url]).to include("#{subgroup.full_path}")
- end
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/cycle_analytics/plan_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/plan_event_fetcher_spec.rb
deleted file mode 100644
index bc14a772d34..00000000000
--- a/spec/lib/gitlab/cycle_analytics/plan_event_fetcher_spec.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::CycleAnalytics::PlanEventFetcher do
- let(:stage_name) { :plan }
-
- it_behaves_like 'default query config' do
- context 'no commits' do
- it 'does not blow up if there are no commits' do
- allow(event).to receive(:event_result).and_return([{}])
-
- expect { event.fetch }.not_to raise_error
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb
deleted file mode 100644
index 2547c05c025..00000000000
--- a/spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb
+++ /dev/null
@@ -1,116 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::CycleAnalytics::PlanStage do
- let(:stage_name) { :plan }
- let(:project) { create(:project) }
- let!(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) }
- let!(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) }
- let!(:issue_3) { create(:issue, project: project, created_at: 30.minutes.ago) }
- let!(:issue_without_milestone) { create(:issue, project: project, created_at: 1.minute.ago) }
- let(:stage_options) { { from: 2.days.ago, current_user: project.creator, project: project } }
- let(:stage) { described_class.new(options: stage_options) }
-
- before do
- issue_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago, first_mentioned_in_commit_at: 10.minutes.ago)
- issue_2.metrics.update!(first_added_to_board_at: 30.minutes.ago, first_mentioned_in_commit_at: 20.minutes.ago)
- issue_3.metrics.update!(first_added_to_board_at: 15.minutes.ago)
- end
-
- it_behaves_like 'base stage'
-
- context 'when using the new query backend' do
- include_examples 'Gitlab::Analytics::CycleAnalytics::DataCollector backend examples' do
- let(:expected_record_count) { 2 }
- let(:expected_ordered_attribute_values) { [issue_1.title, issue_2.title] }
- end
- end
-
- describe '#project_median' do
- around do |example|
- freeze_time { example.run }
- end
-
- it 'counts median from issues with metrics' do
- expect(stage.project_median).to eq(ISSUES_MEDIAN)
- end
-
- include_examples 'calculate #median with date range'
- end
-
- describe '#events' do
- subject { stage.events }
-
- it 'exposes issues with metrics' do
- expect(subject.count).to eq(2)
- expect(subject.map { |event| event[:title] }).to contain_exactly(issue_1.title, issue_2.title)
- end
- end
-
- context 'when group is given' do
- let(:user) { create(:user) }
- let(:group) { create(:group) }
- let(:project_2) { create(:project, group: group) }
- let(:project_3) { create(:project, group: group) }
- let(:issue_2_1) { create(:issue, project: project_2, created_at: 90.minutes.ago) }
- let(:issue_2_2) { create(:issue, project: project_3, created_at: 60.minutes.ago) }
- let(:issue_2_3) { create(:issue, project: project_2, created_at: 60.minutes.ago) }
- let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: user, group: group }) }
-
- before do
- group.add_owner(user)
- issue_2_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago, first_mentioned_in_commit_at: 10.minutes.ago)
- issue_2_2.metrics.update!(first_added_to_board_at: 30.minutes.ago, first_mentioned_in_commit_at: 20.minutes.ago)
- issue_2_3.metrics.update!(first_added_to_board_at: 15.minutes.ago)
- end
-
- describe '#group_median' do
- around do |example|
- freeze_time { example.run }
- end
-
- it 'counts median from issues with metrics' do
- expect(stage.group_median).to eq(ISSUES_MEDIAN)
- end
- end
-
- describe '#events' do
- subject { stage.events }
-
- it 'exposes merge requests that close issues' do
- expect(subject.count).to eq(2)
- expect(subject.map { |event| event[:title] }).to contain_exactly(issue_2_1.title, issue_2_2.title)
- end
- end
-
- context 'when subgroup is given' do
- let(:subgroup) { create(:group, parent: group) }
- let(:project_4) { create(:project, group: subgroup) }
- let(:project_5) { create(:project, group: subgroup) }
- let(:issue_3_1) { create(:issue, project: project_4, created_at: 90.minutes.ago) }
- let(:issue_3_2) { create(:issue, project: project_5, created_at: 60.minutes.ago) }
- let(:issue_3_3) { create(:issue, project: project_5, created_at: 60.minutes.ago) }
-
- before do
- issue_3_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago, first_mentioned_in_commit_at: 10.minutes.ago)
- issue_3_2.metrics.update!(first_added_to_board_at: 30.minutes.ago, first_mentioned_in_commit_at: 20.minutes.ago)
- issue_3_3.metrics.update!(first_added_to_board_at: 15.minutes.ago)
- end
-
- describe '#events' do
- subject { stage.events }
-
- it 'exposes merge requests that close issues' do
- expect(subject.count).to eq(4)
- expect(subject.map { |event| event[:title] }).to contain_exactly(issue_2_1.title, issue_2_2.title, issue_3_1.title, issue_3_2.title)
- end
-
- it 'exposes merge requests that close issues with full path for subgroup' do
- expect(subject.count).to eq(4)
- expect(subject.find { |event| event[:title] == issue_3_1.title }[:url]).to include("#{subgroup.full_path}")
- end
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/cycle_analytics/production_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/production_event_fetcher_spec.rb
deleted file mode 100644
index 86b07a95cbb..00000000000
--- a/spec/lib/gitlab/cycle_analytics/production_event_fetcher_spec.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::CycleAnalytics::ProductionEventFetcher do
- let(:stage_name) { :production }
-
- it_behaves_like 'default query config'
-end
diff --git a/spec/lib/gitlab/cycle_analytics/review_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/review_event_fetcher_spec.rb
deleted file mode 100644
index fe13cc6b065..00000000000
--- a/spec/lib/gitlab/cycle_analytics/review_event_fetcher_spec.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::CycleAnalytics::ReviewEventFetcher do
- let(:stage_name) { :review }
-
- it_behaves_like 'default query config'
-end
diff --git a/spec/lib/gitlab/cycle_analytics/review_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/review_stage_spec.rb
deleted file mode 100644
index 5593013740e..00000000000
--- a/spec/lib/gitlab/cycle_analytics/review_stage_spec.rb
+++ /dev/null
@@ -1,90 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::CycleAnalytics::ReviewStage do
- let(:stage_name) { :review }
- let(:project) { create(:project) }
- let(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) }
- let(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) }
- let(:issue_3) { create(:issue, project: project, created_at: 60.minutes.ago) }
- let(:mr_1) { create(:merge_request, :closed, source_project: project, created_at: 60.minutes.ago) }
- let(:mr_2) { create(:merge_request, :closed, source_project: project, created_at: 40.minutes.ago, source_branch: 'A') }
- let(:mr_3) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'B') }
- let!(:mr_4) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'C') }
- let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: project.creator, project: project }) }
-
- before do
- mr_1.metrics.update!(merged_at: 30.minutes.ago)
- mr_2.metrics.update!(merged_at: 10.minutes.ago)
-
- create(:merge_requests_closing_issues, merge_request: mr_1, issue: issue_1)
- create(:merge_requests_closing_issues, merge_request: mr_2, issue: issue_2)
- create(:merge_requests_closing_issues, merge_request: mr_3, issue: issue_3)
- end
-
- it_behaves_like 'base stage'
-
- describe '#project_median' do
- around do |example|
- freeze_time { example.run }
- end
-
- it 'counts median from issues with metrics' do
- expect(stage.project_median).to eq(ISSUES_MEDIAN)
- end
- end
-
- describe '#events' do
- subject { stage.events }
-
- it 'exposes merge requests that close issues' do
- expect(subject.count).to eq(2)
- expect(subject.map { |event| event[:title] }).to contain_exactly(mr_1.title, mr_2.title)
- end
- end
-
- context 'when group is given' do
- let(:user) { create(:user) }
- let(:group) { create(:group) }
- let(:project_2) { create(:project, group: group) }
- let(:project_3) { create(:project, group: group) }
- let(:issue_2_1) { create(:issue, project: project_2, created_at: 90.minutes.ago) }
- let(:issue_2_2) { create(:issue, project: project_3, created_at: 60.minutes.ago) }
- let(:issue_2_3) { create(:issue, project: project_2, created_at: 60.minutes.ago) }
- let(:mr_2_1) { create(:merge_request, :closed, source_project: project_2, created_at: 60.minutes.ago) }
- let(:mr_2_2) { create(:merge_request, :closed, source_project: project_3, created_at: 40.minutes.ago, source_branch: 'A') }
- let(:mr_2_3) { create(:merge_request, source_project: project_2, created_at: 10.minutes.ago, source_branch: 'B') }
- let!(:mr_2_4) { create(:merge_request, source_project: project_3, created_at: 10.minutes.ago, source_branch: 'C') }
- let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: user, group: group }) }
-
- before do
- group.add_owner(user)
- mr_2_1.metrics.update!(merged_at: 30.minutes.ago)
- mr_2_2.metrics.update!(merged_at: 10.minutes.ago)
-
- create(:merge_requests_closing_issues, merge_request: mr_2_1, issue: issue_2_1)
- create(:merge_requests_closing_issues, merge_request: mr_2_2, issue: issue_2_2)
- create(:merge_requests_closing_issues, merge_request: mr_2_3, issue: issue_2_3)
- end
-
- describe '#group_median' do
- around do |example|
- freeze_time { example.run }
- end
-
- it 'counts median from issues with metrics' do
- expect(stage.group_median).to eq(ISSUES_MEDIAN)
- end
- end
-
- describe '#events' do
- subject { stage.events }
-
- it 'exposes merge requests that close issues' do
- expect(subject.count).to eq(2)
- expect(subject.map { |event| event[:title] }).to contain_exactly(mr_2_1.title, mr_2_2.title)
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/cycle_analytics/staging_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/staging_event_fetcher_spec.rb
deleted file mode 100644
index bdf1b99c4c9..00000000000
--- a/spec/lib/gitlab/cycle_analytics/staging_event_fetcher_spec.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::CycleAnalytics::StagingEventFetcher do
- let(:stage_name) { :staging }
-
- it_behaves_like 'default query config' do
- it 'has a default order' do
- expect(event.order).not_to be_nil
- end
- end
-end
diff --git a/spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb
deleted file mode 100644
index 852f7041dc6..00000000000
--- a/spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb
+++ /dev/null
@@ -1,99 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::CycleAnalytics::StagingStage do
- let(:stage_name) { :staging }
-
- let(:project) { create(:project) }
- let(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) }
- let(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) }
- let(:issue_3) { create(:issue, project: project, created_at: 60.minutes.ago) }
- let(:mr_1) { create(:merge_request, :closed, source_project: project, created_at: 60.minutes.ago) }
- let(:mr_2) { create(:merge_request, :closed, source_project: project, created_at: 40.minutes.ago, source_branch: 'A') }
- let(:mr_3) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'B') }
- let(:build_1) { create(:ci_build, project: project) }
- let(:build_2) { create(:ci_build, project: project) }
-
- let(:stage_options) { { from: 2.days.ago, current_user: project.creator, project: project } }
- let(:stage) { described_class.new(options: stage_options) }
-
- before do
- mr_1.metrics.update!(merged_at: 80.minutes.ago, first_deployed_to_production_at: 50.minutes.ago, pipeline_id: build_1.commit_id)
- mr_2.metrics.update!(merged_at: 60.minutes.ago, first_deployed_to_production_at: 30.minutes.ago, pipeline_id: build_2.commit_id)
- mr_3.metrics.update!(merged_at: 10.minutes.ago, first_deployed_to_production_at: 3.days.ago, pipeline_id: create(:ci_build, project: project).commit_id)
-
- create(:merge_requests_closing_issues, merge_request: mr_1, issue: issue_1)
- create(:merge_requests_closing_issues, merge_request: mr_2, issue: issue_2)
- create(:merge_requests_closing_issues, merge_request: mr_3, issue: issue_3)
- end
-
- it_behaves_like 'base stage'
-
- describe '#project_median' do
- around do |example|
- freeze_time { example.run }
- end
-
- it 'counts median from issues with metrics' do
- expect(stage.project_median).to eq(ISSUES_MEDIAN)
- end
-
- it_behaves_like 'calculate #median with date range'
- end
-
- describe '#events' do
- subject { stage.events }
-
- it 'exposes builds connected to merge request' do
- expect(subject.count).to eq(2)
- expect(subject.map { |event| event[:name] }).to contain_exactly(build_1.name, build_2.name)
- end
- end
-
- context 'when group is given' do
- let(:user) { create(:user) }
- let(:group) { create(:group) }
- let(:project_2) { create(:project, group: group) }
- let(:project_3) { create(:project, group: group) }
- let(:issue_2_1) { create(:issue, project: project_2, created_at: 90.minutes.ago) }
- let(:issue_2_2) { create(:issue, project: project_3, created_at: 60.minutes.ago) }
- let(:issue_2_3) { create(:issue, project: project_2, created_at: 60.minutes.ago) }
- let(:mr_1) { create(:merge_request, :closed, source_project: project_2, created_at: 60.minutes.ago) }
- let(:mr_2) { create(:merge_request, :closed, source_project: project_3, created_at: 40.minutes.ago, source_branch: 'A') }
- let(:mr_3) { create(:merge_request, source_project: project_2, created_at: 10.minutes.ago, source_branch: 'B') }
- let(:build_1) { create(:ci_build, project: project_2) }
- let(:build_2) { create(:ci_build, project: project_3) }
- let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: user, group: group }) }
-
- before do
- group.add_owner(user)
- mr_1.metrics.update!(merged_at: 80.minutes.ago, first_deployed_to_production_at: 50.minutes.ago, pipeline_id: build_1.commit_id)
- mr_2.metrics.update!(merged_at: 60.minutes.ago, first_deployed_to_production_at: 30.minutes.ago, pipeline_id: build_2.commit_id)
- mr_3.metrics.update!(merged_at: 10.minutes.ago, first_deployed_to_production_at: 3.days.ago, pipeline_id: create(:ci_build, project: project_2).commit_id)
-
- create(:merge_requests_closing_issues, merge_request: mr_1, issue: issue_2_1)
- create(:merge_requests_closing_issues, merge_request: mr_2, issue: issue_2_2)
- create(:merge_requests_closing_issues, merge_request: mr_3, issue: issue_2_3)
- end
-
- describe '#group_median' do
- around do |example|
- freeze_time { example.run }
- end
-
- it 'counts median from issues with metrics' do
- expect(stage.group_median).to eq(ISSUES_MEDIAN)
- end
- end
-
- describe '#events' do
- subject { stage.events }
-
- it 'exposes merge requests that close issues' do
- expect(subject.count).to eq(2)
- expect(subject.map { |event| event[:name] }).to contain_exactly(build_1.name, build_2.name)
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/cycle_analytics/test_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/test_event_fetcher_spec.rb
deleted file mode 100644
index 1277385d0b4..00000000000
--- a/spec/lib/gitlab/cycle_analytics/test_event_fetcher_spec.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::CycleAnalytics::TestEventFetcher do
- let(:stage_name) { :test }
-
- it_behaves_like 'default query config' do
- it 'has a default order' do
- expect(event.order).not_to be_nil
- end
- end
-end
diff --git a/spec/lib/gitlab/cycle_analytics/test_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/test_stage_spec.rb
deleted file mode 100644
index 49ee6624260..00000000000
--- a/spec/lib/gitlab/cycle_analytics/test_stage_spec.rb
+++ /dev/null
@@ -1,57 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::CycleAnalytics::TestStage do
- let(:stage_name) { :test }
- let(:project) { create(:project) }
- let(:stage_options) { { from: 2.days.ago, current_user: project.creator, project: project } }
- let(:stage) { described_class.new(options: stage_options) }
-
- it_behaves_like 'base stage'
-
- describe '#median' do
- let(:mr_1) { create(:merge_request, :closed, source_project: project, created_at: 60.minutes.ago) }
- let(:mr_2) { create(:merge_request, :closed, source_project: project, created_at: 40.minutes.ago, source_branch: 'A') }
- let(:mr_3) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'B') }
- let(:mr_4) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'C') }
- let(:mr_5) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'D') }
- let(:ci_build1) { create(:ci_build, project: project) }
- let(:ci_build2) { create(:ci_build, project: project) }
-
- before do
- issue_1 = create(:issue, project: project, created_at: 90.minutes.ago)
- issue_2 = create(:issue, project: project, created_at: 60.minutes.ago)
- issue_3 = create(:issue, project: project, created_at: 60.minutes.ago)
- mr_1.metrics.update!(latest_build_started_at: 32.minutes.ago, latest_build_finished_at: 2.minutes.ago, pipeline_id: ci_build1.commit_id)
- mr_2.metrics.update!(latest_build_started_at: 62.minutes.ago, latest_build_finished_at: 32.minutes.ago, pipeline_id: ci_build2.commit_id)
- mr_3.metrics.update!(latest_build_started_at: nil, latest_build_finished_at: nil)
- mr_4.metrics.update!(latest_build_started_at: nil, latest_build_finished_at: nil)
- mr_5.metrics.update!(latest_build_started_at: nil, latest_build_finished_at: nil)
-
- create(:merge_requests_closing_issues, merge_request: mr_1, issue: issue_1)
- create(:merge_requests_closing_issues, merge_request: mr_2, issue: issue_2)
- create(:merge_requests_closing_issues, merge_request: mr_3, issue: issue_3)
- create(:merge_requests_closing_issues, merge_request: mr_4, issue: issue_3)
- create(:merge_requests_closing_issues, merge_request: mr_5, issue: issue_3)
- end
-
- around do |example|
- freeze_time { example.run }
- end
-
- it 'counts median from issues with metrics' do
- expect(stage.project_median).to eq(ISSUES_MEDIAN)
- end
-
- include_examples 'calculate #median with date range'
-
- context 'when using the new query backend' do
- include_examples 'Gitlab::Analytics::CycleAnalytics::DataCollector backend examples' do
- let(:expected_record_count) { 2 }
- let(:attribute_to_verify) { :id }
- let(:expected_ordered_attribute_values) { [mr_1.metrics.pipeline.builds.first.id, mr_2.metrics.pipeline.builds.first.id] }
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/danger/base_linter_spec.rb b/spec/lib/gitlab/danger/base_linter_spec.rb
index bd0ceb5a125..0136a0278ae 100644
--- a/spec/lib/gitlab/danger/base_linter_spec.rb
+++ b/spec/lib/gitlab/danger/base_linter_spec.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require 'fast_spec_helper'
+require 'rspec-parameterized'
require_relative 'danger_spec_helper'
require 'gitlab/danger/base_linter'
@@ -70,19 +71,57 @@ RSpec.describe Gitlab::Danger::BaseLinter do
end
end
- context 'when subject is a WIP' do
+ context 'when ignoring length issues for subject having not-ready wording' do
+ using RSpec::Parameterized::TableSyntax
+
let(:final_message) { 'A B C' }
- # commit message with prefix will be over max length. commit message without prefix will be of maximum size
- let(:commit_message) { described_class::WIP_PREFIX + final_message + 'D' * (described_class::MAX_LINE_LENGTH - final_message.size) }
- it 'does not have any problems' do
- commit_linter.lint_subject
+ context 'when used as prefix' do
+ where(prefix: [
+ 'WIP: ',
+ 'WIP:',
+ 'wIp:',
+ '[WIP] ',
+ '[WIP]',
+ '[draft]',
+ '[draft] ',
+ '(draft)',
+ '(draft) ',
+ 'draft - ',
+ 'draft: ',
+ 'draft:',
+ 'DRAFT:'
+ ])
+
+ with_them do
+ it 'does not have any problems' do
+ commit_message = prefix + final_message + 'D' * (described_class::MAX_LINE_LENGTH - final_message.size)
+ commit = commit_class.new(commit_message, anything, anything)
+
+ linter = described_class.new(commit).lint_subject
+
+ expect(linter.problems).to be_empty
+ end
+ end
+ end
- expect(commit_linter.problems).to be_empty
+ context 'when used as suffix' do
+ where(suffix: %w[WIP draft])
+
+ with_them do
+ it 'does not have any problems' do
+ commit_message = final_message + 'D' * (described_class::MAX_LINE_LENGTH - final_message.size) + suffix
+ commit = commit_class.new(commit_message, anything, anything)
+
+ linter = described_class.new(commit).lint_subject
+
+ expect(linter.problems).to be_empty
+ end
+ end
end
end
- context 'when subject is too short and too long' do
+ context 'when subject does not have enough words and is too long' do
let(:commit_message) { 'A ' + 'B' * described_class::MAX_LINE_LENGTH }
it 'adds a problem' do
diff --git a/spec/lib/gitlab/danger/changelog_spec.rb b/spec/lib/gitlab/danger/changelog_spec.rb
index 2da60f4f8bd..04c515f1205 100644
--- a/spec/lib/gitlab/danger/changelog_spec.rb
+++ b/spec/lib/gitlab/danger/changelog_spec.rb
@@ -150,41 +150,80 @@ RSpec.describe Gitlab::Danger::Changelog do
end
describe '#modified_text' do
- let(:sanitize_mr_title) { 'Fake Title' }
let(:mr_json) { { "iid" => 1234, "title" => sanitize_mr_title } }
subject { changelog.modified_text }
- it do
- expect(subject).to include('CHANGELOG.md was edited')
- expect(subject).to include('bin/changelog -m 1234 "Fake Title"')
- expect(subject).to include('bin/changelog --ee -m 1234 "Fake Title"')
+ context "when title is not changed from sanitization", :aggregate_failures do
+ let(:sanitize_mr_title) { 'Fake Title' }
+
+ specify do
+ expect(subject).to include('CHANGELOG.md was edited')
+ expect(subject).to include('bin/changelog -m 1234 "Fake Title"')
+ expect(subject).to include('bin/changelog --ee -m 1234 "Fake Title"')
+ end
+ end
+
+ context "when title needs sanitization", :aggregate_failures do
+ let(:sanitize_mr_title) { 'DRAFT: Fake Title' }
+
+ specify do
+ expect(subject).to include('CHANGELOG.md was edited')
+ expect(subject).to include('bin/changelog -m 1234 "Fake Title"')
+ expect(subject).to include('bin/changelog --ee -m 1234 "Fake Title"')
+ end
end
end
describe '#required_text' do
- let(:sanitize_mr_title) { 'Fake Title' }
let(:mr_json) { { "iid" => 1234, "title" => sanitize_mr_title } }
subject { changelog.required_text }
- it do
- expect(subject).to include('CHANGELOG missing')
- expect(subject).to include('bin/changelog -m 1234 "Fake Title"')
- expect(subject).not_to include('--ee')
+ context "when title is not changed from sanitization", :aggregate_failures do
+ let(:sanitize_mr_title) { 'Fake Title' }
+
+ specify do
+ expect(subject).to include('CHANGELOG missing')
+ expect(subject).to include('bin/changelog -m 1234 "Fake Title"')
+ expect(subject).not_to include('--ee')
+ end
+ end
+
+ context "when title needs sanitization", :aggregate_failures do
+ let(:sanitize_mr_title) { 'DRAFT: Fake Title' }
+
+ specify do
+ expect(subject).to include('CHANGELOG missing')
+ expect(subject).to include('bin/changelog -m 1234 "Fake Title"')
+ expect(subject).not_to include('--ee')
+ end
end
end
- describe 'optional_text' do
- let(:sanitize_mr_title) { 'Fake Title' }
+ describe '#optional_text' do
let(:mr_json) { { "iid" => 1234, "title" => sanitize_mr_title } }
subject { changelog.optional_text }
- it do
- expect(subject).to include('CHANGELOG missing')
- expect(subject).to include('bin/changelog -m 1234 "Fake Title"')
- expect(subject).to include('bin/changelog --ee -m 1234 "Fake Title"')
+ context "when title is not changed from sanitization", :aggregate_failures do
+ let(:sanitize_mr_title) { 'Fake Title' }
+
+ specify do
+ expect(subject).to include('CHANGELOG missing')
+ expect(subject).to include('bin/changelog -m 1234 "Fake Title"')
+ expect(subject).to include('bin/changelog --ee -m 1234 "Fake Title"')
+ end
+ end
+
+ context "when title needs sanitization", :aggregate_failures do
+ let(:sanitize_mr_title) { 'DRAFT: Fake Title' }
+
+ specify do
+ expect(subject).to include('CHANGELOG missing')
+ expect(subject).to include('bin/changelog -m 1234 "Fake Title"')
+ expect(subject).to include('bin/changelog --ee -m 1234 "Fake Title"')
+ end
end
end
end
diff --git a/spec/lib/gitlab/danger/helper_spec.rb b/spec/lib/gitlab/danger/helper_spec.rb
index a8f113a8cd1..bd5c746dd54 100644
--- a/spec/lib/gitlab/danger/helper_spec.rb
+++ b/spec/lib/gitlab/danger/helper_spec.rb
@@ -351,33 +351,23 @@ RSpec.describe Gitlab::Danger::Helper do
end
context 'having specific changes' do
- it 'has database and backend categories' do
- changed_files = ['usage_data.rb', 'lib/gitlab/usage_data.rb', 'ee/lib/ee/gitlab/usage_data.rb']
-
- changed_files.each do |file|
- allow(fake_git).to receive(:diff_for_file).with(file) { double(:diff, patch: "+ count(User.active)") }
-
- expect(helper.categories_for_file(file)).to eq([:database, :backend])
- end
- end
-
- it 'has backend category' do
- allow(fake_git).to receive(:diff_for_file).with('usage_data.rb') { double(:diff, patch: "+ alt_usage_data(User.active)") }
-
- expect(helper.categories_for_file('usage_data.rb')).to eq([:backend])
- end
-
- it 'has backend category for changes outside usage_data files' do
- allow(fake_git).to receive(:diff_for_file).with('user.rb') { double(:diff, patch: "+ count(User.active)") }
-
- expect(helper.categories_for_file('user.rb')).to eq([:backend])
+ where(:expected_categories, :patch, :changed_files) do
+ [:database, :backend] | '+ count(User.active)' | ['usage_data.rb', 'lib/gitlab/usage_data.rb', 'ee/lib/ee/gitlab/usage_data.rb']
+ [:database, :backend] | '+ estimate_batch_distinct_count(User.active)' | ['usage_data.rb']
+ [:backend] | '+ alt_usage_data(User.active)' | ['usage_data.rb']
+ [:backend] | '+ count(User.active)' | ['user.rb']
+ [:backend] | '+ count(User.active)' | ['usage_data/topology.rb']
+ [:backend] | '+ foo_count(User.active)' | ['usage_data.rb']
end
- it 'has backend category for files that are not usage_data.rb' do
- changed_file = 'usage_data/topology.rb'
- allow(fake_git).to receive(:diff_for_file).with(changed_file) { double(:diff, patch: "+ count(User.active)") }
+ with_them do
+ it 'has the correct categories' do
+ changed_files.each do |file|
+ allow(fake_git).to receive(:diff_for_file).with(file) { double(:diff, patch: patch) }
- expect(helper.categories_for_file(changed_file)).to eq([:backend])
+ expect(helper.categories_for_file(file)).to eq(expected_categories)
+ end
+ end
end
end
end
@@ -412,24 +402,6 @@ RSpec.describe Gitlab::Danger::Helper do
end
end
- describe '#sanitize_mr_title' do
- where(:mr_title, :expected_mr_title) do
- 'My MR title' | 'My MR title'
- 'WIP: My MR title' | 'My MR title'
- 'Draft: My MR title' | 'My MR title'
- '(Draft) My MR title' | 'My MR title'
- '[Draft] My MR title' | 'My MR title'
- '[DRAFT] My MR title' | 'My MR title'
- 'DRAFT: My MR title' | 'My MR title'
- end
-
- with_them do
- subject { helper.sanitize_mr_title(mr_title) }
-
- it { is_expected.to eq(expected_mr_title) }
- end
- end
-
describe '#security_mr?' do
it 'returns false when `gitlab_helper` is unavailable' do
expect(helper).to receive(:gitlab_helper).and_return(nil)
diff --git a/spec/lib/gitlab/danger/roulette_spec.rb b/spec/lib/gitlab/danger/roulette_spec.rb
index 561e108bf31..59ac3b12b6b 100644
--- a/spec/lib/gitlab/danger/roulette_spec.rb
+++ b/spec/lib/gitlab/danger/roulette_spec.rb
@@ -245,69 +245,6 @@ RSpec.describe Gitlab::Danger::Roulette do
end
end
end
-
- describe 'reviewer suggestion probability' do
- let(:reviewer) { teammate_with_capability('reviewer', 'reviewer backend') }
- let(:hungry_reviewer) { teammate_with_capability('hungry_reviewer', 'reviewer backend', hungry: true) }
- let(:traintainer) { teammate_with_capability('traintainer', 'trainee_maintainer backend') }
- let(:hungry_traintainer) { teammate_with_capability('hungry_traintainer', 'trainee_maintainer backend', hungry: true) }
- let(:teammates) do
- [
- reviewer.to_h,
- hungry_reviewer.to_h,
- traintainer.to_h,
- hungry_traintainer.to_h
- ]
- end
-
- let(:categories) { [:backend] }
-
- # This test is testing probability with inherent randomness.
- # The variance is inversely related to sample size
- # Given large enough sample size, the variance would be smaller,
- # but the test would take longer.
- # Given smaller sample size, the variance would be larger,
- # but the test would take less time.
- let!(:sample_size) { 500 }
- let!(:variance) { 0.1 }
-
- before do
- # This test needs actual randomness to simulate probabilities
- allow(subject).to receive(:new_random).and_return(Random.new)
- WebMock
- .stub_request(:get, described_class::ROULETTE_DATA_URL)
- .to_return(body: teammate_json)
- end
-
- it 'has 1:2:3:4 probability of picking reviewer, hungry_reviewer, traintainer, hungry_traintainer' do
- picks = Array.new(sample_size).map do
- spins = subject.spin(project, categories, timezone_experiment: timezone_experiment)
- spins.first.reviewer.name
- end
-
- expect(probability(picks, 'reviewer')).to be_within(variance).of(0.1)
- expect(probability(picks, 'hungry_reviewer')).to be_within(variance).of(0.2)
- expect(probability(picks, 'traintainer')).to be_within(variance).of(0.3)
- expect(probability(picks, 'hungry_traintainer')).to be_within(variance).of(0.4)
- end
-
- def probability(picks, role)
- picks.count(role).to_f / picks.length
- end
-
- def teammate_with_capability(name, capability, hungry: false)
- Gitlab::Danger::Teammate.new(
- {
- 'name' => name,
- 'projects' => {
- 'gitlab' => capability
- },
- 'available' => true,
- 'hungry' => hungry
- }
- )
- end
- end
end
RSpec::Matchers.define :match_teammates do |expected|
diff --git a/spec/lib/gitlab/danger/teammate_spec.rb b/spec/lib/gitlab/danger/teammate_spec.rb
index eebe14ed5e1..9c066ba4c1b 100644
--- a/spec/lib/gitlab/danger/teammate_spec.rb
+++ b/spec/lib/gitlab/danger/teammate_spec.rb
@@ -121,6 +121,14 @@ RSpec.describe Gitlab::Danger::Teammate do
end
end
+ context 'when capabilities include maintainer engineering productivity' do
+ let(:capabilities) { ['maintainer engineering_productivity'] }
+
+ it '#maintainer? returns true' do
+ expect(subject.maintainer?(project, :engineering_productivity, labels)).to be_truthy
+ end
+ end
+
context 'when capabilities include trainee_maintainer backend' do
let(:capabilities) { ['trainee_maintainer backend'] }
diff --git a/spec/lib/gitlab/danger/title_linting_spec.rb b/spec/lib/gitlab/danger/title_linting_spec.rb
new file mode 100644
index 00000000000..b48d2c5e53d
--- /dev/null
+++ b/spec/lib/gitlab/danger/title_linting_spec.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require 'rspec-parameterized'
+
+require 'gitlab/danger/title_linting'
+
+RSpec.describe Gitlab::Danger::TitleLinting do
+ using RSpec::Parameterized::TableSyntax
+
+ describe '#sanitize_mr_title' do
+ where(:mr_title, :expected_mr_title) do
+ '`My MR title`' | "\\`My MR title\\`"
+ 'WIP: My MR title' | 'My MR title'
+ 'Draft: My MR title' | 'My MR title'
+ '(Draft) My MR title' | 'My MR title'
+ '[Draft] My MR title' | 'My MR title'
+ '[DRAFT] My MR title' | 'My MR title'
+ 'DRAFT: My MR title' | 'My MR title'
+ 'DRAFT: `My MR title`' | "\\`My MR title\\`"
+ end
+
+ with_them do
+ subject { described_class.sanitize_mr_title(mr_title) }
+
+ it { is_expected.to eq(expected_mr_title) }
+ end
+ end
+
+ describe '#remove_draft_flag' do
+ where(:mr_title, :expected_mr_title) do
+ 'WIP: My MR title' | 'My MR title'
+ 'Draft: My MR title' | 'My MR title'
+ '(Draft) My MR title' | 'My MR title'
+ '[Draft] My MR title' | 'My MR title'
+ '[DRAFT] My MR title' | 'My MR title'
+ 'DRAFT: My MR title' | 'My MR title'
+ end
+
+ with_them do
+ subject { described_class.remove_draft_flag(mr_title) }
+
+ it { is_expected.to eq(expected_mr_title) }
+ end
+ end
+
+ describe '#has_draft_flag?' do
+ it 'returns true for a draft title' do
+ expect(described_class.has_draft_flag?('Draft: My MR title')).to be true
+ end
+
+ it 'returns false for non draft title' do
+ expect(described_class.has_draft_flag?('My MR title')).to be false
+ end
+ end
+end
diff --git a/spec/lib/gitlab/danger/weightage/maintainers_spec.rb b/spec/lib/gitlab/danger/weightage/maintainers_spec.rb
new file mode 100644
index 00000000000..066bb487fa2
--- /dev/null
+++ b/spec/lib/gitlab/danger/weightage/maintainers_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require 'gitlab/danger/weightage/maintainers'
+
+RSpec.describe Gitlab::Danger::Weightage::Maintainers do
+ let(:multiplier) { Gitlab::Danger::Weightage::CAPACITY_MULTIPLIER }
+ let(:regular_maintainer) { double('Teammate', reduced_capacity: false) }
+ let(:reduced_capacity_maintainer) { double('Teammate', reduced_capacity: true) }
+ let(:maintainers) do
+ [
+ regular_maintainer,
+ reduced_capacity_maintainer
+ ]
+ end
+
+ let(:maintainer_count) { Gitlab::Danger::Weightage::BASE_REVIEWER_WEIGHT * multiplier }
+ let(:reduced_capacity_maintainer_count) { Gitlab::Danger::Weightage::BASE_REVIEWER_WEIGHT }
+
+ subject(:weighted_maintainers) { described_class.new(maintainers).execute }
+
+ describe '#execute' do
+ it 'weights the maintainers overall' do
+ expect(weighted_maintainers.count).to eq maintainer_count + reduced_capacity_maintainer_count
+ end
+
+ it 'has total count of regular maintainers' do
+ expect(weighted_maintainers.count { |r| r.object_id == regular_maintainer.object_id }).to eq maintainer_count
+ end
+
+ it 'has count of reduced capacity maintainers' do
+ expect(weighted_maintainers.count { |r| r.object_id == reduced_capacity_maintainer.object_id }).to eq reduced_capacity_maintainer_count
+ end
+ end
+end
diff --git a/spec/lib/gitlab/danger/weightage/reviewers_spec.rb b/spec/lib/gitlab/danger/weightage/reviewers_spec.rb
new file mode 100644
index 00000000000..cca81f4d9b5
--- /dev/null
+++ b/spec/lib/gitlab/danger/weightage/reviewers_spec.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+require 'gitlab/danger/weightage/reviewers'
+
+RSpec.describe Gitlab::Danger::Weightage::Reviewers do
+ let(:multiplier) { Gitlab::Danger::Weightage::CAPACITY_MULTIPLIER }
+ let(:regular_reviewer) { double('Teammate', hungry: false, reduced_capacity: false) }
+ let(:hungry_reviewer) { double('Teammate', hungry: true, reduced_capacity: false) }
+ let(:reduced_capacity_reviewer) { double('Teammate', hungry: false, reduced_capacity: true) }
+ let(:reviewers) do
+ [
+ hungry_reviewer,
+ regular_reviewer,
+ reduced_capacity_reviewer
+ ]
+ end
+
+ let(:regular_traintainer) { double('Teammate', hungry: false, reduced_capacity: false) }
+ let(:hungry_traintainer) { double('Teammate', hungry: true, reduced_capacity: false) }
+ let(:reduced_capacity_traintainer) { double('Teammate', hungry: false, reduced_capacity: true) }
+ let(:traintainers) do
+ [
+ hungry_traintainer,
+ regular_traintainer,
+ reduced_capacity_traintainer
+ ]
+ end
+
+ let(:hungry_reviewer_count) { Gitlab::Danger::Weightage::BASE_REVIEWER_WEIGHT * multiplier + described_class::DEFAULT_REVIEWER_WEIGHT }
+ let(:hungry_traintainer_count) { described_class::TRAINTAINER_WEIGHT * multiplier + described_class::DEFAULT_REVIEWER_WEIGHT }
+ let(:reviewer_count) { Gitlab::Danger::Weightage::BASE_REVIEWER_WEIGHT * multiplier }
+ let(:traintainer_count) { Gitlab::Danger::Weightage::BASE_REVIEWER_WEIGHT * described_class::TRAINTAINER_WEIGHT * multiplier }
+ let(:reduced_capacity_reviewer_count) { Gitlab::Danger::Weightage::BASE_REVIEWER_WEIGHT }
+ let(:reduced_capacity_traintainer_count) { described_class::TRAINTAINER_WEIGHT }
+
+ subject(:weighted_reviewers) { described_class.new(reviewers, traintainers).execute }
+
+ describe '#execute', :aggregate_failures do
+ it 'weights the reviewers overall' do
+ reviewers_count = hungry_reviewer_count + reviewer_count + reduced_capacity_reviewer_count
+ traintainers_count = hungry_traintainer_count + traintainer_count + reduced_capacity_traintainer_count
+
+ expect(weighted_reviewers.count).to eq reviewers_count + traintainers_count
+ end
+
+ it 'has total count of hungry reviewers and traintainers' do
+ expect(weighted_reviewers.count(&:hungry)).to eq hungry_reviewer_count + hungry_traintainer_count
+ expect(weighted_reviewers.count { |r| r.object_id == hungry_reviewer.object_id }).to eq hungry_reviewer_count
+ expect(weighted_reviewers.count { |r| r.object_id == hungry_traintainer.object_id }).to eq hungry_traintainer_count
+ end
+
+ it 'has total count of regular reviewers and traintainers' do
+ expect(weighted_reviewers.count { |r| r.object_id == regular_reviewer.object_id }).to eq reviewer_count
+ expect(weighted_reviewers.count { |r| r.object_id == regular_traintainer.object_id }).to eq traintainer_count
+ end
+
+ it 'has count of reduced capacity reviewers' do
+ expect(weighted_reviewers.count(&:reduced_capacity)).to eq reduced_capacity_reviewer_count + reduced_capacity_traintainer_count
+ expect(weighted_reviewers.count { |r| r.object_id == reduced_capacity_reviewer.object_id }).to eq reduced_capacity_reviewer_count
+ expect(weighted_reviewers.count { |r| r.object_id == reduced_capacity_traintainer.object_id }).to eq reduced_capacity_traintainer_count
+ end
+ end
+end
diff --git a/spec/lib/gitlab/data_builder/build_spec.rb b/spec/lib/gitlab/data_builder/build_spec.rb
index cfaaf849b09..2f74e766a11 100644
--- a/spec/lib/gitlab/data_builder/build_spec.rb
+++ b/spec/lib/gitlab/data_builder/build_spec.rb
@@ -26,6 +26,7 @@ RSpec.describe Gitlab::DataBuilder::Build do
it {
expect(data[:user]).to eq(
{
+ id: user.id,
name: user.name,
username: user.username,
avatar_url: user.avatar_url(only_path: false),
diff --git a/spec/lib/gitlab/data_builder/pipeline_spec.rb b/spec/lib/gitlab/data_builder/pipeline_spec.rb
index e5dfff33a2a..297d87708d8 100644
--- a/spec/lib/gitlab/data_builder/pipeline_spec.rb
+++ b/spec/lib/gitlab/data_builder/pipeline_spec.rb
@@ -41,6 +41,7 @@ RSpec.describe Gitlab::DataBuilder::Pipeline do
expect(project_data).to eq(project.hook_attrs(backward: false))
expect(data[:merge_request]).to be_nil
expect(data[:user]).to eq({
+ id: user.id,
name: user.name,
username: user.username,
avatar_url: user.avatar_url(only_path: false),
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index a763dc08b73..6b709cba5b3 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe Gitlab::Database::MigrationHelpers do
+ include Database::TableSchemaHelpers
+
let(:model) do
ActiveRecord::Migration.new.extend(described_class)
end
@@ -96,6 +98,131 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
end
end
+ describe '#create_table_with_constraints' do
+ let(:table_name) { :test_table }
+ let(:column_attributes) do
+ [
+ { name: 'id', sql_type: 'bigint', null: false, default: nil },
+ { name: 'created_at', sql_type: 'timestamp with time zone', null: false, default: nil },
+ { name: 'updated_at', sql_type: 'timestamp with time zone', null: false, default: nil },
+ { name: 'some_id', sql_type: 'integer', null: false, default: nil },
+ { name: 'active', sql_type: 'boolean', null: false, default: 'true' },
+ { name: 'name', sql_type: 'text', null: true, default: nil }
+ ]
+ end
+
+ before do
+ allow(model).to receive(:transaction_open?).and_return(true)
+ end
+
+ context 'when no check constraints are defined' do
+ it 'creates the table as expected' do
+ model.create_table_with_constraints table_name do |t|
+ t.timestamps_with_timezone
+ t.integer :some_id, null: false
+ t.boolean :active, null: false, default: true
+ t.text :name
+ end
+
+ expect_table_columns_to_match(column_attributes, table_name)
+ end
+ end
+
+ context 'when check constraints are defined' do
+ context 'when the text_limit is explicity named' do
+ it 'creates the table as expected' do
+ model.create_table_with_constraints table_name do |t|
+ t.timestamps_with_timezone
+ t.integer :some_id, null: false
+ t.boolean :active, null: false, default: true
+ t.text :name
+
+ t.text_limit :name, 255, name: 'check_name_length'
+ t.check_constraint :some_id_is_positive, 'some_id > 0'
+ end
+
+ expect_table_columns_to_match(column_attributes, table_name)
+
+ expect_check_constraint(table_name, 'check_name_length', 'char_length(name) <= 255')
+ expect_check_constraint(table_name, 'some_id_is_positive', 'some_id > 0')
+ end
+ end
+
+ context 'when the text_limit is not named' do
+ it 'creates the table as expected, naming the text limit' do
+ model.create_table_with_constraints table_name do |t|
+ t.timestamps_with_timezone
+ t.integer :some_id, null: false
+ t.boolean :active, null: false, default: true
+ t.text :name
+
+ t.text_limit :name, 255
+ t.check_constraint :some_id_is_positive, 'some_id > 0'
+ end
+
+ expect_table_columns_to_match(column_attributes, table_name)
+
+ expect_check_constraint(table_name, 'check_cda6f69506', 'char_length(name) <= 255')
+ expect_check_constraint(table_name, 'some_id_is_positive', 'some_id > 0')
+ end
+ end
+
+ it 'runs the change within a with_lock_retries' do
+ expect(model).to receive(:with_lock_retries).ordered.and_yield
+ expect(model).to receive(:create_table).ordered.and_call_original
+ expect(model).to receive(:execute).with(<<~SQL).ordered
+ ALTER TABLE "#{table_name}"\nADD CONSTRAINT "check_cda6f69506" CHECK (char_length("name") <= 255)
+ SQL
+
+ model.create_table_with_constraints table_name do |t|
+ t.text :name
+ t.text_limit :name, 255
+ end
+ end
+
+ context 'when constraints are given invalid names' do
+ let(:expected_max_length) { described_class::MAX_IDENTIFIER_NAME_LENGTH }
+ let(:expected_error_message) { "The maximum allowed constraint name is #{expected_max_length} characters" }
+
+ context 'when the explicit text limit name is not valid' do
+ it 'raises an error' do
+ too_long_length = expected_max_length + 1
+
+ expect do
+ model.create_table_with_constraints table_name do |t|
+ t.timestamps_with_timezone
+ t.integer :some_id, null: false
+ t.boolean :active, null: false, default: true
+ t.text :name
+
+ t.text_limit :name, 255, name: ('a' * too_long_length)
+ t.check_constraint :some_id_is_positive, 'some_id > 0'
+ end
+ end.to raise_error(expected_error_message)
+ end
+ end
+
+ context 'when a check constraint name is not valid' do
+ it 'raises an error' do
+ too_long_length = expected_max_length + 1
+
+ expect do
+ model.create_table_with_constraints table_name do |t|
+ t.timestamps_with_timezone
+ t.integer :some_id, null: false
+ t.boolean :active, null: false, default: true
+ t.text :name
+
+ t.text_limit :name, 255
+ t.check_constraint ('a' * too_long_length), 'some_id > 0'
+ end
+ end.to raise_error(expected_error_message)
+ end
+ end
+ end
+ end
+ end
+
describe '#add_concurrent_index' do
context 'outside a transaction' do
before do
@@ -1548,6 +1675,69 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
end
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)
+ end
+
+ context 'in a transaction' do
+ it 'raises RuntimeError' do
+ allow(model).to receive(:transaction_open?).and_return(true)
+
+ expect { model.initialize_conversion_of_integer_to_bigint(:events, :id) }
+ .to raise_error(RuntimeError)
+ end
+ end
+
+ context 'outside a transaction' do
+ before do
+ allow(model).to receive(:transaction_open?).and_return(false)
+ 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
+ )
+
+ expect(model)
+ .to receive(:install_rename_triggers)
+ .with(:events, :id, 'id_convert_to_bigint')
+
+ expect(model).to receive(:queue_background_migration_jobs_by_range_at_intervals).and_call_original
+
+ expect(BackgroundMigrationWorker)
+ .to receive(:perform_in)
+ .ordered
+ .with(
+ 2.minutes,
+ 'CopyColumnUsingBackgroundMigrationJob',
+ [event.id, event.id, :events, :id, :id, 'id_convert_to_bigint', 100]
+ )
+
+ expect(Gitlab::BackgroundMigration)
+ .to receive(:steal)
+ .ordered
+ .with('CopyColumnUsingBackgroundMigrationJob')
+
+ model.initialize_conversion_of_integer_to_bigint(
+ :events,
+ :id,
+ batch_size: 300,
+ sub_batch_size: 100
+ )
+ end
+ end
+ end
+
describe '#index_exists_by_name?' do
it 'returns true if an index exists' do
ActiveRecord::Base.connection.execute(
diff --git a/spec/lib/gitlab/database/partitioning/partition_creator_spec.rb b/spec/lib/gitlab/database/partitioning/partition_creator_spec.rb
index 56399941662..ec89f2ed61c 100644
--- a/spec/lib/gitlab/database/partitioning/partition_creator_spec.rb
+++ b/spec/lib/gitlab/database/partitioning/partition_creator_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Gitlab::Database::Partitioning::PartitionCreator do
- include PartitioningHelpers
+ include Database::PartitioningHelpers
include ExclusiveLeaseHelpers
describe '.register' do
diff --git a/spec/lib/gitlab/database/partitioning/replace_table_spec.rb b/spec/lib/gitlab/database/partitioning/replace_table_spec.rb
index d47666eeffd..8e27797208c 100644
--- a/spec/lib/gitlab/database/partitioning/replace_table_spec.rb
+++ b/spec/lib/gitlab/database/partitioning/replace_table_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Gitlab::Database::Partitioning::ReplaceTable, '#perform' do
- include TableSchemaHelpers
+ include Database::TableSchemaHelpers
subject(:replace_table) { described_class.new(original_table, replacement_table, archived_table, 'id').perform }
diff --git a/spec/lib/gitlab/database/partitioning_migration_helpers/foreign_key_helpers_spec.rb b/spec/lib/gitlab/database/partitioning_migration_helpers/foreign_key_helpers_spec.rb
index 7d88c17c9b3..93dbd9d7c30 100644
--- a/spec/lib/gitlab/database/partitioning_migration_helpers/foreign_key_helpers_spec.rb
+++ b/spec/lib/gitlab/database/partitioning_migration_helpers/foreign_key_helpers_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::ForeignKeyHelpers do
- include TriggerHelpers
+ include Database::TriggerHelpers
let(:model) do
ActiveRecord::Migration.new.extend(described_class)
diff --git a/spec/lib/gitlab/database/partitioning_migration_helpers/index_helpers_spec.rb b/spec/lib/gitlab/database/partitioning_migration_helpers/index_helpers_spec.rb
index 7f61ff759fc..603f3dc41af 100644
--- a/spec/lib/gitlab/database/partitioning_migration_helpers/index_helpers_spec.rb
+++ b/spec/lib/gitlab/database/partitioning_migration_helpers/index_helpers_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::IndexHelpers do
- include TableSchemaHelpers
+ include Database::TableSchemaHelpers
let(:migration) do
ActiveRecord::Migration.new.extend(described_class)
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 f10ff704c17..b50e02c7043 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
@@ -3,25 +3,36 @@
require 'spec_helper'
RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers do
- include PartitioningHelpers
- include TriggerHelpers
- include TableSchemaHelpers
+ include Database::PartitioningHelpers
+ include Database::TriggerHelpers
+ include Database::TableSchemaHelpers
let(:migration) do
ActiveRecord::Migration.new.extend(described_class)
end
let_it_be(:connection) { ActiveRecord::Base.connection }
- let(:source_table) { :audit_events }
+ let(:source_table) { :_test_original_table }
let(:partitioned_table) { '_test_migration_partitioned_table' }
let(:function_name) { '_test_migration_function_name' }
let(:trigger_name) { '_test_migration_trigger_name' }
let(:partition_column) { 'created_at' }
let(:min_date) { Date.new(2019, 12) }
let(:max_date) { Date.new(2020, 3) }
+ let(:source_model) { Class.new(ActiveRecord::Base) }
before do
allow(migration).to receive(:puts)
+
+ migration.create_table source_table do |t|
+ t.string :name, null: false
+ t.integer :age, null: false
+ t.datetime partition_column
+ t.datetime :updated_at
+ end
+
+ source_model.table_name = source_table
+
allow(migration).to receive(:transaction_open?).and_return(false)
allow(migration).to receive(:make_partitioned_table_name).and_return(partitioned_table)
allow(migration).to receive(:make_sync_function_name).and_return(function_name)
@@ -81,14 +92,11 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHe
end
context 'when the given table does not have a primary key' do
- let(:source_table) { :_partitioning_migration_helper_test_table }
- let(:partition_column) { :some_field }
-
it 'raises an error' do
- migration.create_table source_table, id: false do |t|
- t.integer :id
- t.datetime partition_column
- end
+ migration.execute(<<~SQL)
+ ALTER TABLE #{source_table}
+ DROP CONSTRAINT #{source_table}_pkey
+ SQL
expect do
migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
@@ -97,12 +105,12 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHe
end
context 'when an invalid partition column is given' do
- let(:partition_column) { :_this_is_not_real }
+ let(:invalid_column) { :_this_is_not_real }
it 'raises an error' do
expect do
- migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
- end.to raise_error(/partition column #{partition_column} does not exist/)
+ migration.partition_table_by_date source_table, invalid_column, min_date: min_date, max_date: max_date
+ end.to raise_error(/partition column #{invalid_column} does not exist/)
end
end
@@ -126,19 +134,19 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHe
context 'with a non-integer primary key datatype' do
before do
- connection.create_table :another_example, id: false do |t|
+ connection.create_table non_int_table, id: false do |t|
t.string :identifier, primary_key: true
t.timestamp :created_at
end
end
- let(:source_table) { :another_example }
+ let(:non_int_table) { :another_example }
let(:old_primary_key) { 'identifier' }
it 'does not change the primary key datatype' do
- migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
+ migration.partition_table_by_date non_int_table, partition_column, min_date: min_date, max_date: max_date
- original_pk_column = connection.columns(source_table).find { |c| c.name == old_primary_key }
+ original_pk_column = connection.columns(non_int_table).find { |c| c.name == old_primary_key }
pk_column = connection.columns(partitioned_table).find { |c| c.name == old_primary_key }
expect(pk_column).not_to be_nil
@@ -176,11 +184,9 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHe
end
context 'when min_date is not given' do
- let(:source_table) { :todos }
-
context 'with records present already' do
before do
- create(:todo, created_at: Date.parse('2019-11-05'))
+ source_model.create!(name: 'Test', age: 10, created_at: Date.parse('2019-11-05'))
end
it 'creates a partition spanning over each month from the first record' do
@@ -248,13 +254,12 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHe
end
describe 'keeping data in sync with the partitioned table' do
- let(:source_table) { :todos }
- let(:model) { Class.new(ActiveRecord::Base) }
+ let(:partitioned_model) { Class.new(ActiveRecord::Base) }
let(:timestamp) { Time.utc(2019, 12, 1, 12).round }
before do
- model.primary_key = :id
- model.table_name = partitioned_table
+ partitioned_model.primary_key = :id
+ partitioned_model.table_name = partitioned_table
end
it 'creates a trigger function on the original table' do
@@ -270,50 +275,50 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHe
it 'syncs inserts to the partitioned tables' do
migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
- expect(model.count).to eq(0)
+ expect(partitioned_model.count).to eq(0)
- first_todo = create(:todo, created_at: timestamp, updated_at: timestamp)
- second_todo = create(:todo, created_at: timestamp, updated_at: timestamp)
+ first_record = source_model.create!(name: 'Bob', age: 20, created_at: timestamp, updated_at: timestamp)
+ second_record = source_model.create!(name: 'Alice', age: 30, created_at: timestamp, updated_at: timestamp)
- expect(model.count).to eq(2)
- expect(model.find(first_todo.id).attributes).to eq(first_todo.attributes)
- expect(model.find(second_todo.id).attributes).to eq(second_todo.attributes)
+ expect(partitioned_model.count).to eq(2)
+ expect(partitioned_model.find(first_record.id).attributes).to eq(first_record.attributes)
+ expect(partitioned_model.find(second_record.id).attributes).to eq(second_record.attributes)
end
it 'syncs updates to the partitioned tables' do
migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
- first_todo = create(:todo, :pending, commit_id: nil, created_at: timestamp, updated_at: timestamp)
- second_todo = create(:todo, created_at: timestamp, updated_at: timestamp)
+ first_record = source_model.create!(name: 'Bob', age: 20, created_at: timestamp, updated_at: timestamp)
+ second_record = source_model.create!(name: 'Alice', age: 30, created_at: timestamp, updated_at: timestamp)
- expect(model.count).to eq(2)
+ expect(partitioned_model.count).to eq(2)
- first_copy = model.find(first_todo.id)
- second_copy = model.find(second_todo.id)
+ first_copy = partitioned_model.find(first_record.id)
+ second_copy = partitioned_model.find(second_record.id)
- expect(first_copy.attributes).to eq(first_todo.attributes)
- expect(second_copy.attributes).to eq(second_todo.attributes)
+ expect(first_copy.attributes).to eq(first_record.attributes)
+ expect(second_copy.attributes).to eq(second_record.attributes)
- first_todo.update(state_event: 'done', commit_id: 'abc123', updated_at: timestamp + 1.second)
+ first_record.update!(age: 21, updated_at: timestamp + 1.hour)
- expect(model.count).to eq(2)
- expect(first_copy.reload.attributes).to eq(first_todo.attributes)
- expect(second_copy.reload.attributes).to eq(second_todo.attributes)
+ expect(partitioned_model.count).to eq(2)
+ expect(first_copy.reload.attributes).to eq(first_record.attributes)
+ expect(second_copy.reload.attributes).to eq(second_record.attributes)
end
it 'syncs deletes to the partitioned tables' do
migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
- first_todo = create(:todo, created_at: timestamp, updated_at: timestamp)
- second_todo = create(:todo, created_at: timestamp, updated_at: timestamp)
+ first_record = source_model.create!(name: 'Bob', age: 20, created_at: timestamp, updated_at: timestamp)
+ second_record = source_model.create!(name: 'Alice', age: 30, created_at: timestamp, updated_at: timestamp)
- expect(model.count).to eq(2)
+ expect(partitioned_model.count).to eq(2)
- first_todo.destroy
+ first_record.destroy!
- expect(model.count).to eq(1)
- expect(model.find_by_id(first_todo.id)).to be_nil
- expect(model.find(second_todo.id).attributes).to eq(second_todo.attributes)
+ expect(partitioned_model.count).to eq(1)
+ expect(partitioned_model.find_by_id(first_record.id)).to be_nil
+ expect(partitioned_model.find(second_record.id).attributes).to eq(second_record.attributes)
end
end
end
@@ -388,13 +393,12 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHe
end
context 'when records exist in the source table' do
- let(:source_table) { 'todos' }
let(:migration_class) { '::Gitlab::Database::PartitioningMigrationHelpers::BackfillPartitionedTable' }
let(:sub_batch_size) { described_class::SUB_BATCH_SIZE }
let(:pause_seconds) { described_class::PAUSE_SECONDS }
- let!(:first_id) { create(:todo).id }
- let!(:second_id) { create(:todo).id }
- let!(:third_id) { create(:todo).id }
+ let!(:first_id) { source_model.create!(name: 'Bob', age: 20).id }
+ let!(:second_id) { source_model.create!(name: 'Alice', age: 30).id }
+ let!(:third_id) { source_model.create!(name: 'Sam', age: 40).id }
before do
stub_const("#{described_class.name}::BATCH_SIZE", 2)
@@ -410,10 +414,10 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHe
expect(BackgroundMigrationWorker.jobs.size).to eq(2)
- first_job_arguments = [first_id, second_id, source_table, partitioned_table, 'id']
+ first_job_arguments = [first_id, second_id, source_table.to_s, partitioned_table, 'id']
expect(BackgroundMigrationWorker.jobs[0]['args']).to eq([migration_class, first_job_arguments])
- second_job_arguments = [third_id, third_id, source_table, partitioned_table, 'id']
+ second_job_arguments = [third_id, third_id, source_table.to_s, partitioned_table, 'id']
expect(BackgroundMigrationWorker.jobs[1]['args']).to eq([migration_class, second_job_arguments])
end
end
@@ -482,7 +486,6 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHe
end
describe '#finalize_backfilling_partitioned_table' do
- let(:source_table) { 'todos' }
let(:source_column) { 'id' }
context 'when the table is not allowed' do
@@ -536,27 +539,27 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHe
context 'when there is missed data' do
let(:partitioned_model) { Class.new(ActiveRecord::Base) }
let(:timestamp) { Time.utc(2019, 12, 1, 12).round }
- let!(:todo1) { create(:todo, created_at: timestamp, updated_at: timestamp) }
- let!(:todo2) { create(:todo, created_at: timestamp, updated_at: timestamp) }
- let!(:todo3) { create(:todo, created_at: timestamp, updated_at: timestamp) }
- let!(:todo4) { create(:todo, created_at: timestamp, updated_at: timestamp) }
+ let!(:record1) { source_model.create!(name: 'Bob', age: 20, created_at: timestamp, updated_at: timestamp) }
+ let!(:record2) { source_model.create!(name: 'Alice', age: 30, created_at: timestamp, updated_at: timestamp) }
+ let!(:record3) { source_model.create!(name: 'Sam', age: 40, created_at: timestamp, updated_at: timestamp) }
+ let!(:record4) { source_model.create!(name: 'Sue', age: 50, created_at: timestamp, updated_at: timestamp) }
let!(:pending_job1) do
create(:background_migration_job,
class_name: described_class::MIGRATION_CLASS_NAME,
- arguments: [todo1.id, todo2.id, source_table, partitioned_table, source_column])
+ arguments: [record1.id, record2.id, source_table, partitioned_table, source_column])
end
let!(:pending_job2) do
create(:background_migration_job,
class_name: described_class::MIGRATION_CLASS_NAME,
- arguments: [todo3.id, todo3.id, source_table, partitioned_table, source_column])
+ arguments: [record3.id, record3.id, source_table, partitioned_table, source_column])
end
let!(:succeeded_job) do
create(:background_migration_job, :succeeded,
class_name: described_class::MIGRATION_CLASS_NAME,
- arguments: [todo4.id, todo4.id, source_table, partitioned_table, source_column])
+ arguments: [record4.id, record4.id, source_table, partitioned_table, source_column])
end
before do
@@ -575,17 +578,17 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHe
it 'idempotently cleans up after failed background migrations' do
expect(partitioned_model.count).to eq(0)
- partitioned_model.insert!(todo2.attributes)
+ partitioned_model.insert!(record2.attributes)
expect_next_instance_of(Gitlab::Database::PartitioningMigrationHelpers::BackfillPartitionedTable) do |backfill|
allow(backfill).to receive(:transaction_open?).and_return(false)
expect(backfill).to receive(:perform)
- .with(todo1.id, todo2.id, source_table, partitioned_table, source_column)
+ .with(record1.id, record2.id, source_table, partitioned_table, source_column)
.and_call_original
expect(backfill).to receive(:perform)
- .with(todo3.id, todo3.id, source_table, partitioned_table, source_column)
+ .with(record3.id, record3.id, source_table, partitioned_table, source_column)
.and_call_original
end
@@ -593,12 +596,12 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHe
expect(partitioned_model.count).to eq(3)
- [todo1, todo2, todo3].each do |original|
+ [record1, record2, record3].each do |original|
copy = partitioned_model.find(original.id)
expect(copy.attributes).to eq(original.attributes)
end
- expect(partitioned_model.find_by_id(todo4.id)).to be_nil
+ expect(partitioned_model.find_by_id(record4.id)).to be_nil
[pending_job1, pending_job2].each do |job|
expect(job.reload).to be_succeeded
diff --git a/spec/lib/gitlab/database/postgres_hll/batch_distinct_counter_spec.rb b/spec/lib/gitlab/database/postgres_hll/batch_distinct_counter_spec.rb
index 934e2274358..2c550f14a08 100644
--- a/spec/lib/gitlab/database/postgres_hll/batch_distinct_counter_spec.rb
+++ b/spec/lib/gitlab/database/postgres_hll/batch_distinct_counter_spec.rb
@@ -24,107 +24,48 @@ RSpec.describe Gitlab::Database::PostgresHll::BatchDistinctCounter do
allow(ActiveRecord::Base.connection).to receive(:transaction_open?).and_return(in_transaction)
end
- context 'different distribution of relation records' do
- [10, 100, 100_000].each do |spread|
- context "records are spread within #{spread}" do
- before do
- ids = (1..spread).to_a.sample(10)
- create_list(:issue, 10).each_with_index do |issue, i|
- issue.id = ids[i]
- end
- end
-
- it 'counts table' do
- expect(described_class.new(model).estimate_distinct_count).to be_within(error_rate).percent_of(10)
- end
- end
- end
- end
-
context 'unit test for different counting parameters' do
before_all do
create_list(:issue, 3, author: user)
create_list(:issue, 2, author: another_user)
end
- describe '#estimate_distinct_count' do
- it 'counts table' do
- expect(described_class.new(model).estimate_distinct_count).to be_within(error_rate).percent_of(5)
- end
-
- it 'counts with column field' do
- expect(described_class.new(model, column).estimate_distinct_count).to be_within(error_rate).percent_of(2)
- end
-
- it 'counts with :id field' do
- expect(described_class.new(model, :id).estimate_distinct_count).to be_within(error_rate).percent_of(5)
- end
-
- it 'counts with "id" field' do
- expect(described_class.new(model, "id").estimate_distinct_count).to be_within(error_rate).percent_of(5)
- end
-
- it 'counts with table.column field' do
- expect(described_class.new(model, "#{model.table_name}.#{column}").estimate_distinct_count).to be_within(error_rate).percent_of(2)
- end
-
- it 'counts with Arel column' do
- expect(described_class.new(model, model.arel_table[column]).estimate_distinct_count).to be_within(error_rate).percent_of(2)
- end
-
- it 'counts over joined relations' do
- expect(described_class.new(model.joins(:author), "users.email").estimate_distinct_count).to be_within(error_rate).percent_of(2)
- end
-
- it 'counts with :column field with batch_size of 50K' do
- expect(described_class.new(model, column).estimate_distinct_count(batch_size: 50_000)).to be_within(error_rate).percent_of(2)
- end
-
- it 'will not count table with a batch size less than allowed' do
- expect(described_class.new(model, column).estimate_distinct_count(batch_size: small_batch_size)).to eq(fallback)
- end
-
- it 'counts with different number of batches and aggregates total result' do
- stub_const('Gitlab::Database::PostgresHll::BatchDistinctCounter::MIN_REQUIRED_BATCH_SIZE', 0)
-
- [1, 2, 4, 5, 6].each { |i| expect(described_class.new(model).estimate_distinct_count(batch_size: i)).to be_within(error_rate).percent_of(5) }
- end
-
- it 'counts with a start and finish' do
- expect(described_class.new(model, column).estimate_distinct_count(start: model.minimum(:id), finish: model.maximum(:id))).to be_within(error_rate).percent_of(2)
+ describe '#execute' do
+ it 'builds hll buckets' do
+ expect(described_class.new(model).execute).to be_an_instance_of(Gitlab::Database::PostgresHll::Buckets)
end
- it "defaults the batch size to #{Gitlab::Database::PostgresHll::BatchDistinctCounter::DEFAULT_BATCH_SIZE}" do
+ it "defaults batch size to #{Gitlab::Database::PostgresHll::BatchDistinctCounter::DEFAULT_BATCH_SIZE}" do
min_id = model.minimum(:id)
batch_end_id = min_id + calculate_batch_size(Gitlab::Database::PostgresHll::BatchDistinctCounter::DEFAULT_BATCH_SIZE)
expect(model).to receive(:where).with("id" => min_id..batch_end_id).and_call_original
- described_class.new(model).estimate_distinct_count
+ described_class.new(model).execute
end
context 'when a transaction is open' do
let(:in_transaction) { true }
it 'raises an error' do
- expect { described_class.new(model, column).estimate_distinct_count }.to raise_error('BatchCount can not be run inside a transaction')
+ expect { described_class.new(model, column).execute }.to raise_error('BatchCount can not be run inside a transaction')
end
end
context 'disallowed configurations' do
let(:default_batch_size) { Gitlab::Database::PostgresHll::BatchDistinctCounter::DEFAULT_BATCH_SIZE }
- it 'returns fallback if start is bigger than finish' do
- expect(described_class.new(model, column).estimate_distinct_count(start: 1, finish: 0)).to eq(fallback)
+ it 'raises WRONG_CONFIGURATION_ERROR if start is bigger than finish' do
+ expect { described_class.new(model, column).execute(start: 1, finish: 0) }.to raise_error(described_class::WRONG_CONFIGURATION_ERROR)
end
- it 'returns fallback if data volume exceeds upper limit' do
+ it 'raises WRONG_CONFIGURATION_ERROR if data volume exceeds upper limit' do
large_finish = Gitlab::Database::PostgresHll::BatchDistinctCounter::MAX_DATA_VOLUME + 1
- expect(described_class.new(model, column).estimate_distinct_count(start: 1, finish: large_finish)).to eq(fallback)
+ expect { described_class.new(model, column).execute(start: 1, finish: large_finish) }.to raise_error(described_class::WRONG_CONFIGURATION_ERROR)
end
- it 'returns fallback if batch size is less than min required' do
- expect(described_class.new(model, column).estimate_distinct_count(batch_size: small_batch_size)).to eq(fallback)
+ it 'raises WRONG_CONFIGURATION_ERROR if batch size is less than min required' do
+ expect { described_class.new(model, column).execute(batch_size: small_batch_size) }.to raise_error(described_class::WRONG_CONFIGURATION_ERROR)
end
end
end
diff --git a/spec/lib/gitlab/database/postgres_hll/buckets_spec.rb b/spec/lib/gitlab/database/postgres_hll/buckets_spec.rb
new file mode 100644
index 00000000000..b4d8fd4a449
--- /dev/null
+++ b/spec/lib/gitlab/database/postgres_hll/buckets_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::PostgresHll::Buckets do
+ let(:error_rate) { Gitlab::Database::PostgresHll::BatchDistinctCounter::ERROR_RATE } # HyperLogLog is a probabilistic algorithm, which provides estimated data, with given error margin
+ let(:buckets_hash_5) { { 121 => 2, 126 => 1, 141 => 1, 383 => 1, 56 => 1 } }
+ let(:buckets_hash_2) { { 141 => 1, 56 => 1 } }
+
+ describe '#estimated_distinct_count' do
+ it 'provides estimated cardinality', :aggregate_failures do
+ expect(described_class.new(buckets_hash_5).estimated_distinct_count).to be_within(error_rate).percent_of(5)
+ expect(described_class.new(buckets_hash_2).estimated_distinct_count).to be_within(error_rate).percent_of(2)
+ expect(described_class.new({}).estimated_distinct_count).to eq 0
+ expect(described_class.new.estimated_distinct_count).to eq 0
+ end
+ end
+
+ describe '#merge_hash!' do
+ let(:hash_a) { { 1 => 1, 2 => 3 } }
+ let(:hash_b) { { 1 => 2, 2 => 1 } }
+
+ it 'merges two hashes together into union of two sets' do
+ expect(described_class.new(hash_a).merge_hash!(hash_b).to_json).to eq described_class.new(1 => 2, 2 => 3).to_json
+ end
+ end
+
+ describe '#to_json' do
+ it 'serialize HyperLogLog buckets as hash' do
+ expect(described_class.new(1 => 5).to_json).to eq '{"1":5}'
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/reindexing/coordinator_spec.rb b/spec/lib/gitlab/database/reindexing/coordinator_spec.rb
index f45d959c0de..ae6362ba812 100644
--- a/spec/lib/gitlab/database/reindexing/coordinator_spec.rb
+++ b/spec/lib/gitlab/database/reindexing/coordinator_spec.rb
@@ -3,65 +3,79 @@
require 'spec_helper'
RSpec.describe Gitlab::Database::Reindexing::Coordinator do
+ include Database::DatabaseHelpers
include ExclusiveLeaseHelpers
describe '.perform' do
- subject { described_class.new(indexes).perform }
+ subject { described_class.new(index, notifier).perform }
- let(:indexes) { [instance_double(Gitlab::Database::PostgresIndex), instance_double(Gitlab::Database::PostgresIndex)] }
- let(:reindexers) { [instance_double(Gitlab::Database::Reindexing::ConcurrentReindex), instance_double(Gitlab::Database::Reindexing::ConcurrentReindex)] }
+ before do
+ swapout_view_for_table(:postgres_indexes)
+
+ allow(Gitlab::Database::Reindexing::ConcurrentReindex).to receive(:new).with(index).and_return(reindexer)
+ allow(Gitlab::Database::Reindexing::ReindexAction).to receive(:create_for).with(index).and_return(action)
+ end
+
+ let(:index) { create(:postgres_index) }
+ let(:notifier) { instance_double(Gitlab::Database::Reindexing::GrafanaNotifier, notify_start: nil, notify_end: nil) }
+ let(:reindexer) { instance_double(Gitlab::Database::Reindexing::ConcurrentReindex, perform: nil) }
+ let(:action) { create(:reindex_action, index: index) }
let!(:lease) { stub_exclusive_lease(lease_key, uuid, timeout: lease_timeout) }
let(:lease_key) { 'gitlab/database/reindexing/coordinator' }
let(:lease_timeout) { 1.day }
let(:uuid) { 'uuid' }
- before do
- allow(Gitlab::Database::Reindexing::ReindexAction).to receive(:keep_track_of).and_yield
+ context 'locking' do
+ it 'acquires a lock while reindexing' do
+ expect(lease).to receive(:try_obtain).ordered.and_return(uuid)
- indexes.zip(reindexers).each do |index, reindexer|
- allow(Gitlab::Database::Reindexing::ConcurrentReindex).to receive(:new).with(index).and_return(reindexer)
- allow(reindexer).to receive(:perform)
- end
- end
+ expect(reindexer).to receive(:perform).ordered
- it 'performs concurrent reindexing for each index' do
- indexes.zip(reindexers).each do |index, reindexer|
- expect(Gitlab::Database::Reindexing::ConcurrentReindex).to receive(:new).with(index).ordered.and_return(reindexer)
- expect(reindexer).to receive(:perform)
+ expect(Gitlab::ExclusiveLease).to receive(:cancel).ordered.with(lease_key, uuid)
+
+ subject
end
- subject
+ it 'does not perform reindexing actions if lease is not granted' do
+ expect(lease).to receive(:try_obtain).ordered.and_return(false)
+ expect(Gitlab::Database::Reindexing::ConcurrentReindex).not_to receive(:new)
+
+ subject
+ end
end
- it 'keeps track of actions and creates ReindexAction records' do
- indexes.each do |index|
- expect(Gitlab::Database::Reindexing::ReindexAction).to receive(:keep_track_of).with(index).and_yield
+ context 'notifications' do
+ it 'sends #notify_start before reindexing' do
+ expect(notifier).to receive(:notify_start).with(action).ordered
+ expect(reindexer).to receive(:perform).ordered
+
+ subject
end
- subject
+ it 'sends #notify_end after reindexing and updating the action is done' do
+ expect(action).to receive(:finish).ordered
+ expect(notifier).to receive(:notify_end).with(action).ordered
+
+ subject
+ end
end
- context 'locking' do
- it 'acquires a lock while reindexing' do
- indexes.each do |index|
- expect(lease).to receive(:try_obtain).ordered.and_return(uuid)
- action = instance_double(Gitlab::Database::Reindexing::ConcurrentReindex)
- expect(Gitlab::Database::Reindexing::ConcurrentReindex).to receive(:new).ordered.with(index).and_return(action)
- expect(action).to receive(:perform).ordered
- expect(Gitlab::ExclusiveLease).to receive(:cancel).ordered.with(lease_key, uuid)
- end
+ context 'action tracking' do
+ it 'calls #finish on the action' do
+ expect(reindexer).to receive(:perform).ordered
+ expect(action).to receive(:finish).ordered
subject
end
- it 'does does not perform reindexing actions if lease is not granted' do
- indexes.each do |index|
- expect(lease).to receive(:try_obtain).ordered.and_return(false)
- expect(Gitlab::Database::Reindexing::ConcurrentReindex).not_to receive(:new)
- end
+ it 'upon error, it still calls finish and raises the error' do
+ expect(reindexer).to receive(:perform).ordered.and_raise('something went wrong')
+ expect(action).to receive(:finish).ordered
- subject
+ expect { subject }.to raise_error(/something went wrong/)
+
+ expect(action).to be_failed
end
end
end
diff --git a/spec/lib/gitlab/database/reindexing/grafana_notifier_spec.rb b/spec/lib/gitlab/database/reindexing/grafana_notifier_spec.rb
new file mode 100644
index 00000000000..e76718fe48a
--- /dev/null
+++ b/spec/lib/gitlab/database/reindexing/grafana_notifier_spec.rb
@@ -0,0 +1,139 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::Reindexing::GrafanaNotifier do
+ include Database::DatabaseHelpers
+
+ let(:api_key) { "foo" }
+ let(:api_url) { "http://bar"}
+ let(:additional_tag) { "some-tag" }
+
+ let(:action) { create(:reindex_action) }
+
+ before do
+ swapout_view_for_table(:postgres_indexes)
+ end
+
+ let(:headers) do
+ {
+ 'Content-Type': 'application/json',
+ 'Authorization': "Bearer #{api_key}"
+ }
+ end
+
+ let(:response) { double('response', success?: true) }
+
+ def expect_api_call(payload)
+ expect(Gitlab::HTTP).to receive(:post).with("#{api_url}/api/annotations", body: payload.to_json, headers: headers, allow_local_requests: true).and_return(response)
+ end
+
+ shared_examples_for 'interacting with Grafana annotations API' do
+ it 'POSTs a JSON payload' do
+ expect_api_call(payload)
+
+ expect(subject).to be_truthy
+ end
+
+ context 'on error' do
+ it 'does not raise the error and returns false' do
+ allow(Gitlab::HTTP).to receive(:post).and_raise('something went wrong')
+
+ expect(subject).to be_falsey
+ end
+
+ context 'when request was not successful' do
+ it 'returns false' do
+ expect_api_call(payload)
+ allow(response).to receive(:success?).and_return(false)
+
+ expect(subject).to be_falsey
+ end
+ end
+ end
+
+ context 'without api_key' do
+ let(:api_key) { '' }
+
+ it 'does not post anything' do
+ expect(Gitlab::HTTP).not_to receive(:post)
+
+ expect(subject).to be_falsey
+ end
+ end
+
+ context 'without api_url' do
+ let(:api_url) { '' }
+
+ it 'does not post anything' do
+ expect(Gitlab::HTTP).not_to receive(:post)
+
+ expect(subject).to be_falsey
+ end
+ end
+ end
+
+ describe '#notify_start' do
+ context 'additional tag is nil' do
+ subject { described_class.new(api_key, api_url, nil).notify_start(action) }
+
+ let(:payload) do
+ {
+ time: (action.action_start.utc.to_f * 1000).to_i,
+ tags: ['reindex', action.index.tablename, action.index.name],
+ text: "Started reindexing of #{action.index.name} on #{action.index.tablename}"
+ }
+ end
+
+ it_behaves_like 'interacting with Grafana annotations API'
+ end
+
+ context 'additional tag is not nil' do
+ subject { described_class.new(api_key, api_url, additional_tag).notify_start(action) }
+
+ let(:payload) do
+ {
+ time: (action.action_start.utc.to_f * 1000).to_i,
+ tags: ['reindex', additional_tag, action.index.tablename, action.index.name],
+ text: "Started reindexing of #{action.index.name} on #{action.index.tablename}"
+ }
+ end
+
+ it_behaves_like 'interacting with Grafana annotations API'
+ end
+ end
+
+ describe '#notify_end' do
+ context 'additional tag is nil' do
+ subject { described_class.new(api_key, api_url, nil).notify_end(action) }
+
+ let(:payload) do
+ {
+ time: (action.action_start.utc.to_f * 1000).to_i,
+ tags: ['reindex', action.index.tablename, action.index.name],
+ text: "Finished reindexing of #{action.index.name} on #{action.index.tablename} (#{action.state})",
+ timeEnd: (action.action_end.utc.to_f * 1000).to_i,
+ isRegion: true
+ }
+ end
+
+ it_behaves_like 'interacting with Grafana annotations API'
+ end
+
+ context 'additional tag is not nil' do
+ subject { described_class.new(api_key, api_url, additional_tag).notify_end(action) }
+
+ let(:payload) do
+ {
+ time: (action.action_start.utc.to_f * 1000).to_i,
+ tags: ['reindex', additional_tag, action.index.tablename, action.index.name],
+ text: "Finished reindexing of #{action.index.name} on #{action.index.tablename} (#{action.state})",
+ timeEnd: (action.action_end.utc.to_f * 1000).to_i,
+ isRegion: true
+ }
+ end
+
+ it_behaves_like 'interacting with Grafana annotations API'
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/reindexing/index_selection_spec.rb b/spec/lib/gitlab/database/reindexing/index_selection_spec.rb
index a5e2f368f40..4466679a099 100644
--- a/spec/lib/gitlab/database/reindexing/index_selection_spec.rb
+++ b/spec/lib/gitlab/database/reindexing/index_selection_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Gitlab::Database::Reindexing::IndexSelection do
- include DatabaseHelpers
+ include Database::DatabaseHelpers
subject { described_class.new(Gitlab::Database::PostgresIndex.all).to_a }
diff --git a/spec/lib/gitlab/database/reindexing/reindex_action_spec.rb b/spec/lib/gitlab/database/reindexing/reindex_action_spec.rb
index 225f23d2135..a8f196d8f0e 100644
--- a/spec/lib/gitlab/database/reindexing/reindex_action_spec.rb
+++ b/spec/lib/gitlab/database/reindexing/reindex_action_spec.rb
@@ -2,91 +2,83 @@
require 'spec_helper'
-RSpec.describe Gitlab::Database::Reindexing::ReindexAction, '.keep_track_of' do
- let(:index) { double('index', identifier: 'public.something', ondisk_size_bytes: 10240, reload: nil, bloat_size: 42) }
- let(:size_after) { 512 }
+RSpec.describe Gitlab::Database::Reindexing::ReindexAction do
+ include Database::DatabaseHelpers
- it 'yields to the caller' do
- expect { |b| described_class.keep_track_of(index, &b) }.to yield_control
- end
+ let(:index) { create(:postgres_index) }
- def find_record
- described_class.find_by(index_identifier: index.identifier)
+ before_all do
+ swapout_view_for_table(:postgres_indexes)
end
- it 'creates the record with a start time and updates its end time' do
- freeze_time do
- described_class.keep_track_of(index) do
- expect(find_record.action_start).to be_within(1.second).of(Time.zone.now)
+ describe '.create_for' do
+ subject { described_class.create_for(index) }
- travel(10.seconds)
- end
+ it 'creates a new record for the given index' do
+ freeze_time do
+ record = subject
- duration = find_record.action_end - find_record.action_start
+ expect(record.index_identifier).to eq(index.identifier)
+ expect(record.action_start).to eq(Time.zone.now)
+ expect(record.ondisk_size_bytes_start).to eq(index.ondisk_size_bytes)
+ expect(subject.bloat_estimate_bytes_start).to eq(index.bloat_size)
- expect(duration).to be_within(1.second).of(10.seconds)
+ expect(record).to be_persisted
+ end
end
end
- it 'creates the record with its status set to :started and updates its state to :finished' do
- described_class.keep_track_of(index) do
- expect(find_record).to be_started
- end
+ describe '#finish' do
+ subject { action.finish }
- expect(find_record).to be_finished
- end
+ let(:action) { build(:reindex_action, index: index) }
- it 'creates the record with the indexes start size and updates its end size' do
- described_class.keep_track_of(index) do
- expect(find_record.ondisk_size_bytes_start).to eq(index.ondisk_size_bytes)
+ it 'sets #action_end' do
+ freeze_time do
+ subject
- expect(index).to receive(:reload).once
- allow(index).to receive(:ondisk_size_bytes).and_return(size_after)
+ expect(action.action_end).to eq(Time.zone.now)
+ end
end
- expect(find_record.ondisk_size_bytes_end).to eq(size_after)
- end
+ it 'sets #ondisk_size_bytes_end after reloading the index record' do
+ new_size = 4711
+ expect(action.index).to receive(:reload).ordered
+ expect(action.index).to receive(:ondisk_size_bytes).and_return(new_size).ordered
+
+ subject
- it 'creates the record with the indexes bloat estimate' do
- described_class.keep_track_of(index) do
- expect(find_record.bloat_estimate_bytes_start).to eq(index.bloat_size)
+ expect(action.ondisk_size_bytes_end).to eq(new_size)
end
- end
- context 'in case of errors' do
- it 'sets the state to failed' do
- expect do
- described_class.keep_track_of(index) do
- raise 'something went wrong'
- end
- end.to raise_error(/something went wrong/)
+ context 'setting #state' do
+ it 'sets #state to finished if not given' do
+ action.state = nil
- expect(find_record).to be_failed
- end
+ subject
- it 'records the end time' do
- freeze_time do
- expect do
- described_class.keep_track_of(index) do
- raise 'something went wrong'
- end
- end.to raise_error(/something went wrong/)
+ expect(action).to be_finished
+ end
+
+ it 'sets #state to finished if not set to started' do
+ action.state = :started
- expect(find_record.action_end).to be_within(1.second).of(Time.zone.now)
+ subject
+
+ expect(action).to be_finished
end
- end
- it 'records the resulting index size' do
- expect(index).to receive(:reload).once
- allow(index).to receive(:ondisk_size_bytes).and_return(size_after)
+ it 'does not change state if set to failed' do
+ action.state = :failed
+
+ expect { subject }.not_to change { action.state }
+ end
+ end
- expect do
- described_class.keep_track_of(index) do
- raise 'something went wrong'
- end
- end.to raise_error(/something went wrong/)
+ it 'saves the record' do
+ expect(action).to receive(:save!)
- expect(find_record.ondisk_size_bytes_end).to eq(size_after)
+ subject
end
end
end
diff --git a/spec/lib/gitlab/database/reindexing_spec.rb b/spec/lib/gitlab/database/reindexing_spec.rb
index eb78a5fe8ea..b2f038e8b62 100644
--- a/spec/lib/gitlab/database/reindexing_spec.rb
+++ b/spec/lib/gitlab/database/reindexing_spec.rb
@@ -11,13 +11,16 @@ RSpec.describe Gitlab::Database::Reindexing do
let(:coordinator) { instance_double(Gitlab::Database::Reindexing::Coordinator) }
let(:index_selection) { instance_double(Gitlab::Database::Reindexing::IndexSelection) }
let(:candidate_indexes) { double }
- let(:indexes) { double }
+ let(:indexes) { [double, double] }
it 'delegates to Coordinator' do
expect(Gitlab::Database::Reindexing::IndexSelection).to receive(:new).with(candidate_indexes).and_return(index_selection)
expect(index_selection).to receive(:take).with(2).and_return(indexes)
- expect(Gitlab::Database::Reindexing::Coordinator).to receive(:new).with(indexes).and_return(coordinator)
- expect(coordinator).to receive(:perform)
+
+ indexes.each do |index|
+ expect(Gitlab::Database::Reindexing::Coordinator).to receive(:new).with(index).and_return(coordinator)
+ expect(coordinator).to receive(:perform)
+ end
subject
end
diff --git a/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb b/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb
index 4048fc69591..417bf3e363a 100644
--- a/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb
+++ b/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb
@@ -8,8 +8,8 @@ RSpec.describe Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService
let(:prometheus_settings) do
{
- enable: true,
- listen_address: 'localhost:9090'
+ enabled: true,
+ server_address: 'localhost:9090'
}
end
@@ -63,13 +63,13 @@ RSpec.describe Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService
application_setting.update(allow_local_requests_from_web_hooks_and_services: true)
end
- shared_examples 'has prometheus service' do |listen_address|
+ shared_examples 'has prometheus service' do |server_address|
it do
expect(result[:status]).to eq(:success)
prometheus = project.prometheus_service
expect(prometheus).not_to eq(nil)
- expect(prometheus.api_url).to eq(listen_address)
+ expect(prometheus.api_url).to eq(server_address)
expect(prometheus.active).to eq(true)
expect(prometheus.manual_configuration).to eq(true)
end
@@ -202,25 +202,25 @@ RSpec.describe Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService
end
context 'with non default prometheus address' do
- let(:listen_address) { 'https://localhost:9090' }
+ let(:server_address) { 'https://localhost:9090' }
let(:prometheus_settings) do
{
- enable: true,
- listen_address: listen_address
+ enabled: true,
+ server_address: server_address
}
end
it_behaves_like 'has prometheus service', 'https://localhost:9090'
context 'with :9090 symbol' do
- let(:listen_address) { :':9090' }
+ let(:server_address) { :':9090' }
it_behaves_like 'has prometheus service', 'http://localhost:9090'
end
context 'with 0.0.0.0:9090' do
- let(:listen_address) { '0.0.0.0:9090' }
+ let(:server_address) { '0.0.0.0:9090' }
it_behaves_like 'has prometheus service', 'http://localhost:9090'
end
@@ -251,8 +251,8 @@ RSpec.describe Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService
context 'when prometheus setting is disabled in gitlab.yml' do
let(:prometheus_settings) do
{
- enable: false,
- listen_address: 'http://localhost:9090'
+ enabled: false,
+ server_address: 'http://localhost:9090'
}
end
@@ -262,8 +262,8 @@ RSpec.describe Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService
end
end
- context 'when prometheus listen address is blank in gitlab.yml' do
- let(:prometheus_settings) { { enable: true, listen_address: '' } }
+ context 'when prometheus server address is blank in gitlab.yml' do
+ let(:prometheus_settings) { { enabled: true, server_address: '' } }
it 'does not configure prometheus' do
expect(result).to include(status: :success)
@@ -296,8 +296,8 @@ RSpec.describe Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService
context 'when prometheus manual configuration cannot be saved' do
let(:prometheus_settings) do
{
- enable: true,
- listen_address: 'httpinvalid://localhost:9090'
+ enabled: true,
+ server_address: 'httpinvalid://localhost:9090'
}
end
diff --git a/spec/lib/gitlab/diff/position_spec.rb b/spec/lib/gitlab/diff/position_spec.rb
index a7f6ea0cbfb..c9a20f40462 100644
--- a/spec/lib/gitlab/diff/position_spec.rb
+++ b/spec/lib/gitlab/diff/position_spec.rb
@@ -752,4 +752,62 @@ RSpec.describe Gitlab::Diff::Position do
expect(subject.file_hash).to eq(Digest::SHA1.hexdigest(subject.file_path))
end
end
+
+ describe '#multiline?' do
+ let(:end_line_code) { "ab09011fa121d0a2bb9fa4ca76094f2482b902b7_#{end_old_line}_#{end_new_line}" }
+
+ let(:line_range) do
+ {
+ "start" => {
+ "line_code" => "ab09011fa121d0a2bb9fa4ca76094f2482b902b7_18_18",
+ "type" => nil,
+ "old_line" => 18,
+ "new_line" => 18
+ },
+ "end" => {
+ "line_code" => end_line_code,
+ "type" => nil,
+ "old_line" => end_old_line,
+ "new_line" => end_new_line
+ }
+ }
+ end
+
+ subject(:multiline) do
+ described_class.new(
+ line_range: line_range,
+ position_type: position_type
+ )
+ end
+
+ let(:end_old_line) { 20 }
+ let(:end_new_line) { 20 }
+
+ context 'when the position type is text' do
+ let(:position_type) { "text" }
+
+ context 'when the start lines equal the end lines' do
+ let(:end_old_line) { 18 }
+ let(:end_new_line) { 18 }
+
+ it "returns true" do
+ expect(subject.multiline?).to be_falsey
+ end
+ end
+
+ context 'when the start lines do not equal the end lines' do
+ it "returns true" do
+ expect(subject.multiline?).to be_truthy
+ end
+ end
+ end
+
+ context 'when the position type is not text' do
+ let(:position_type) { "image" }
+
+ it "returns false" do
+ expect(subject.multiline?).to be_falsey
+ 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 ef448ee96a4..8872800069a 100644
--- a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
@@ -4,146 +4,50 @@ require 'spec_helper'
RSpec.describe Gitlab::Email::Handler::CreateNoteHandler do
include_context :email_shared_context
- let!(:sent_notification) do
- SentNotification.record_note(note, user.id, mail_key)
- end
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, :public, :repository) }
let(:noteable) { note.noteable }
let(:note) { create(:diff_note_on_merge_request, project: project) }
- let(:user) { create(:user) }
- let(:project) { create(:project, :public, :repository) }
let(:email_raw) { fixture_file('emails/valid_reply.eml') }
+ let!(:sent_notification) do
+ SentNotification.record_note(note, user.id, mail_key)
+ end
it_behaves_like :reply_processing_shared_examples
+ it_behaves_like :note_handler_shared_examples do
+ let(:recipient) { sent_notification.recipient }
+
+ let(:update_commands_only) { fixture_file('emails/update_commands_only_reply.eml')}
+ let(:no_content) { fixture_file('emails/no_content_reply.eml') }
+ let(:commands_in_reply) { fixture_file('emails/commands_in_reply.eml') }
+ let(:with_quick_actions) { fixture_file('emails/valid_reply_with_quick_actions.eml') }
+ end
+
before do
stub_incoming_email_setting(enabled: true, address: "reply+%{key}@appmail.adventuretime.ooo")
stub_config_setting(host: 'localhost')
end
- context "when the recipient address doesn't include a mail key" do
- let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(mail_key, "") }
+ context 'when the recipient address does not include a mail key' do
+ let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(mail_key, '') }
- it "raises a UnknownIncomingEmail" do
+ it 'raises a UnknownIncomingEmail' do
expect { receiver.execute }.to raise_error(Gitlab::Email::UnknownIncomingEmail)
end
end
- context "when no sent notification for the mail key could be found" do
+ context 'when no sent notification for the mail key could be found' do
let(:email_raw) { fixture_file('emails/wrong_mail_key.eml') }
- it "raises a SentNotificationNotFoundError" do
+ it 'raises a SentNotificationNotFoundError' do
expect { receiver.execute }.to raise_error(Gitlab::Email::SentNotificationNotFoundError)
end
end
- context "when the noteable could not be found" do
- before do
- noteable.destroy
- end
-
- it "raises a NoteableNotFoundError" do
- expect { receiver.execute }.to raise_error(Gitlab::Email::NoteableNotFoundError)
- end
- end
-
- context "when the note could not be saved" do
- before do
- allow_any_instance_of(Note).to receive(:persisted?).and_return(false)
- end
-
- it "raises an InvalidNoteError" do
- expect { receiver.execute }.to raise_error(Gitlab::Email::InvalidNoteError)
- end
-
- context 'because the note was update commands only' do
- let!(:email_raw) { fixture_file("emails/update_commands_only_reply.eml") }
-
- context 'and current user cannot update noteable' do
- it 'raises a CommandsOnlyNoteError' do
- expect { receiver.execute }.to raise_error(Gitlab::Email::InvalidNoteError)
- end
- end
-
- context "and current user can update noteable" do
- before do
- project.add_developer(user)
- end
-
- it 'does not raise an error' do
- expect { receiver.execute }.to change { noteable.resource_state_events.count }.by(1)
-
- expect(noteable.reload).to be_closed
- end
- end
- end
- end
-
- context 'when the note contains quick actions' do
- let!(:email_raw) { fixture_file("emails/commands_in_reply.eml") }
-
- context 'and current user cannot update the noteable' do
- it 'only executes the commands that the user can perform' do
- expect { receiver.execute }
- .to change { noteable.notes.user.count }.by(1)
- .and change { user.todos_pending_count }.from(0).to(1)
-
- expect(noteable.reload).to be_open
- end
- end
-
- context 'and current user can update noteable' do
- before do
- project.add_developer(user)
- end
-
- it 'posts a note and updates the noteable' do
- expect(TodoService.new.todo_exist?(noteable, user)).to be_falsy
-
- expect { receiver.execute }
- .to change { noteable.notes.user.count }.by(1)
- .and change { user.todos_pending_count }.from(0).to(1)
-
- expect(noteable.reload).to be_closed
- end
- end
- end
-
- context "when the reply is blank" do
- let!(:email_raw) { fixture_file("emails/no_content_reply.eml") }
-
- it "raises an EmptyEmailError" do
- expect { receiver.execute }.to raise_error(Gitlab::Email::EmptyEmailError)
- end
- end
-
- shared_examples "checks permissions on noteable" do
- context "when user has access" do
- before do
- project.add_reporter(user)
- end
-
- it "creates a comment" do
- expect { receiver.execute }.to change { noteable.notes.count }.by(1)
- end
- end
-
- context "when user does not have access" do
- it "raises UserNotAuthorizedError" do
- expect { receiver.execute }.to raise_error(Gitlab::Email::UserNotAuthorizedError)
- end
- end
- end
-
- context "when discussion is locked" do
- before do
- noteable.update_attribute(:discussion_locked, true)
- end
-
- it_behaves_like "checks permissions on noteable"
- end
-
- context "when issue is confidential" do
+ context 'when issue is confidential' do
let(:issue) { create(:issue, project: project) }
let(:note) { create(:note, noteable: issue, project: project) }
@@ -151,17 +55,17 @@ RSpec.describe Gitlab::Email::Handler::CreateNoteHandler do
issue.update_attribute(:confidential, true)
end
- it_behaves_like "checks permissions on noteable"
+ it_behaves_like :checks_permissions_on_noteable_examples
end
shared_examples 'a reply to existing comment' do
- it "creates a comment" do
+ it 'creates a comment' do
expect { receiver.execute }.to change { noteable.notes.count }.by(1)
new_note = noteable.notes.last
expect(new_note.author).to eq(sent_notification.recipient)
expect(new_note.position).to eq(note.position)
- expect(new_note.note).to include("I could not disagree more.")
+ 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?
@@ -172,32 +76,14 @@ RSpec.describe Gitlab::Email::Handler::CreateNoteHandler do
end
end
- context "when everything is fine" do
+ # additional shared tests in :reply_processing_shared_examples
+ context 'when everything is fine' do
before do
setup_attachment
end
it_behaves_like 'a reply to existing comment'
- it "adds all attachments" do
- expect_next_instance_of(Gitlab::Email::AttachmentUploader) do |uploader|
- expect(uploader).to receive(:execute).with(upload_parent: project, uploader_class: FileUploader).and_return(
- [
- {
- url: "uploads/image.png",
- alt: "image",
- markdown: markdown
- }
- ]
- )
- end
-
- receiver.execute
-
- note = noteable.notes.last
- expect(note.note).to include(markdown)
- end
-
context 'when sub-addressing is not supported' do
before do
stub_incoming_email_setting(enabled: true, address: nil)
@@ -228,75 +114,9 @@ RSpec.describe Gitlab::Email::Handler::CreateNoteHandler do
end
end
- context "when note is not a discussion" do
+ context 'when note is not a discussion' do
let(:note) { create(:note_on_merge_request, project: project) }
it_behaves_like 'a reply to existing comment'
end
-
- context 'when the service desk' do
- let(:project) { create(:project, :public, service_desk_enabled: true) }
- let(:support_bot) { User.support_bot }
- let(:noteable) { create(:issue, project: project, author: support_bot, title: 'service desk issue') }
- let(:note) { create(:note, project: project, noteable: noteable) }
- let(:email_raw) { fixture_file('emails/valid_reply_with_quick_actions.eml') }
-
- let!(:sent_notification) do
- SentNotification.record_note(note, support_bot.id, mail_key)
- end
-
- context 'is enabled' do
- before do
- allow(Gitlab::ServiceDesk).to receive(:enabled?).with(project: project).and_return(true)
- project.project_feature.update!(issues_access_level: issues_access_level)
- end
-
- context 'when issues are enabled for everyone' do
- let(:issues_access_level) { ProjectFeature::ENABLED }
-
- it 'creates a comment' do
- expect { receiver.execute }.to change { noteable.notes.count }.by(1)
- end
-
- context 'when quick actions are present' do
- it 'encloses quick actions with code span markdown' do
- receiver.execute
- noteable.reload
-
- note = Note.last
- expect(note.note).to include("Jake out\n\n`/close`\n`/title test`")
- expect(noteable.title).to eq('service desk issue')
- expect(noteable).to be_opened
- end
- end
- end
-
- context 'when issues are protected members only' do
- let(:issues_access_level) { ProjectFeature::PRIVATE }
-
- it 'creates a comment' do
- expect { receiver.execute }.to change { noteable.notes.count }.by(1)
- end
- end
-
- context 'when issues are disabled' do
- let(:issues_access_level) { ProjectFeature::DISABLED }
-
- it 'does not create a comment' do
- expect { receiver.execute }.to raise_error(Gitlab::Email::UserNotAuthorizedError)
- end
- end
- end
-
- context 'is disabled' do
- before do
- allow(Gitlab::ServiceDesk).to receive(:enabled?).and_return(false)
- allow(Gitlab::ServiceDesk).to receive(:enabled?).with(project: project).and_return(false)
- end
-
- it 'does not create a comment' do
- expect { receiver.execute }.to raise_error(Gitlab::Email::ProjectNotFound)
- end
- end
- 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
new file mode 100644
index 00000000000..94f28d3399a
--- /dev/null
+++ b/spec/lib/gitlab/email/handler/create_note_on_issuable_handler_spec.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Email::Handler::CreateNoteOnIssuableHandler do
+ include_context :email_shared_context
+
+ let_it_be(:user) { create(:user, email: 'jake@adventuretime.ooo', incoming_email_token: 'auth_token') }
+ let_it_be(:namespace) { create(:namespace, path: 'gitlabhq') }
+ let_it_be(:project) { create(:project, :public, namespace: namespace, path: 'gitlabhq') }
+
+ let!(:noteable) { create(:issue, project: project) }
+ let(:email_raw) { email_fixture('emails/valid_note_on_issuable.eml') }
+
+ before do
+ stub_incoming_email_setting(enabled: true, address: "incoming+%{key}@appmail.adventuretime.ooo")
+ stub_config_setting(host: 'localhost')
+ end
+
+ it_behaves_like :reply_processing_shared_examples
+
+ 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(: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') }
+ end
+
+ context 'when the recipient address does not include a mail key' do
+ let(:mail_key) { 'gitlabhq-gitlabhq-project_id-auth_token-issue-issue_iid' }
+ let(:email_raw) { fixture_file('emails/valid_note_on_issuable.eml').gsub(mail_key, '') }
+
+ it 'raises an UnknownIncomingEmail' do
+ expect { receiver.execute }.to raise_error(Gitlab::Email::UnknownIncomingEmail)
+ end
+ end
+
+ context 'when issue is confidential' do
+ before do
+ noteable.update_attribute(:confidential, true)
+ end
+
+ it_behaves_like :checks_permissions_on_noteable_examples
+ end
+
+ def email_fixture(path)
+ fixture_file(path)
+ .gsub('project_id', project.project_id.to_s)
+ .gsub('issue_iid', noteable.iid.to_s)
+ end
+
+ def email_reply_fixture(path)
+ reply_address = 'reply+59d8df8370b7e95c5a49fbf86aeb2c93'
+ note_address = "incoming+#{project.full_path_slug}-#{project.project_id}-#{user.incoming_email_token}-issue-#{noteable.iid}"
+
+ fixture_file(path)
+ .gsub(reply_address, note_address)
+ end
+end
diff --git a/spec/lib/gitlab/email/handler/service_desk_handler_spec.rb b/spec/lib/gitlab/email/handler/service_desk_handler_spec.rb
index 32b451f8329..b1ffbedc7bf 100644
--- a/spec/lib/gitlab/email/handler/service_desk_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/service_desk_handler_spec.rb
@@ -191,16 +191,6 @@ RSpec.describe Gitlab::Email::Handler::ServiceDeskHandler do
expect { receiver.execute }.to raise_error(Gitlab::Email::ProjectNotFound)
end
end
-
- context 'when service_desk_custom_address feature is disabled' do
- before do
- stub_feature_flags(service_desk_custom_address: false)
- end
-
- it 'bounces the email' do
- expect { receiver.execute }.to raise_error(Gitlab::Email::ProjectNotFound)
- end
- end
end
end
diff --git a/spec/lib/gitlab/email/handler_spec.rb b/spec/lib/gitlab/email/handler_spec.rb
index 2cd8c31e6b2..eff6fb63a5f 100644
--- a/spec/lib/gitlab/email/handler_spec.rb
+++ b/spec/lib/gitlab/email/handler_spec.rb
@@ -60,8 +60,9 @@ RSpec.describe Gitlab::Email::Handler do
describe 'regexps are set properly' do
let(:addresses) do
- %W(sent_notification_key#{Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX} sent_notification_key path-to-project-123-user_email_token-merge-request) +
- %W(sent_notification_key#{Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX_LEGACY} sent_notification_key path-to-project-123-user_email_token-issue) +
+ %W(sent_notification_key#{Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX} sent_notification_key#{Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX_LEGACY}) +
+ %w(sent_notification_key path-to-project-123-user_email_token-merge-request) +
+ %w(path-to-project-123-user_email_token-issue path-to-project-123-user_email_token-issue-123) +
%w(path/to/project+user_email_token path/to/project+merge-request+user_email_token some/project)
end
diff --git a/spec/lib/gitlab/error_tracking_spec.rb b/spec/lib/gitlab/error_tracking_spec.rb
index 68a46b11487..764478ad1d7 100644
--- a/spec/lib/gitlab/error_tracking_spec.rb
+++ b/spec/lib/gitlab/error_tracking_spec.rb
@@ -236,7 +236,7 @@ RSpec.describe Gitlab::ErrorTracking do
context 'the exception implements :sentry_extra_data' do
let(:extra_info) { { event: 'explosion', size: :massive } }
- let(:exception) { double(message: 'bang!', sentry_extra_data: extra_info, backtrace: caller) }
+ let(:exception) { double(message: 'bang!', sentry_extra_data: extra_info, backtrace: caller, cause: nil) }
it 'includes the extra data from the exception in the tracking information' do
track_exception
@@ -247,7 +247,7 @@ RSpec.describe Gitlab::ErrorTracking do
end
context 'the exception implements :sentry_extra_data, which returns nil' do
- let(:exception) { double(message: 'bang!', sentry_extra_data: nil, backtrace: caller) }
+ let(:exception) { double(message: 'bang!', sentry_extra_data: nil, backtrace: caller, cause: nil) }
let(:extra) { { issue_url: issue_url } }
it 'just includes the other extra info' do
@@ -287,10 +287,23 @@ RSpec.describe Gitlab::ErrorTracking 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
+ 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
+
track_exception
+ end
+ end
- expect(Raven).to have_received(:capture_exception)
- .with(exception, a_hash_including(extra: a_hash_including(sql: 'SELECT "users".* FROM "users" WHERE "users"."id" = $2 AND "users"."foo" = $1')))
+ 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')) }
+
+ 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
+
+ track_exception
end
end
end
diff --git a/spec/lib/gitlab/experimentation/controller_concern_spec.rb b/spec/lib/gitlab/experimentation/controller_concern_spec.rb
index 03cb89ee033..c47f71c207d 100644
--- a/spec/lib/gitlab/experimentation/controller_concern_spec.rb
+++ b/spec/lib/gitlab/experimentation/controller_concern_spec.rb
@@ -156,6 +156,16 @@ RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do
is_expected.to eq(true)
end
end
+
+ context 'Cookie parameter to force enable experiment' do
+ it 'returns true unconditionally' do
+ cookies[:force_experiment] = 'test_experiment,another_experiment'
+ get :index
+
+ expect(check_experiment(:test_experiment)).to eq(true)
+ expect(check_experiment(:another_experiment)).to eq(true)
+ end
+ end
end
describe '#track_experiment_event', :snowplow do
diff --git a/spec/lib/gitlab/experimentation/experiment_spec.rb b/spec/lib/gitlab/experimentation/experiment_spec.rb
index 7b1d1763010..008e6699597 100644
--- a/spec/lib/gitlab/experimentation/experiment_spec.rb
+++ b/spec/lib/gitlab/experimentation/experiment_spec.rb
@@ -14,8 +14,10 @@ RSpec.describe Gitlab::Experimentation::Experiment do
end
before do
- feature = double('FeatureFlag', percentage_of_time_value: percentage )
- expect(Feature).to receive(:get).with(:experiment_key_experiment_percentage).and_return(feature)
+ skip_feature_flags_yaml_validation
+ skip_default_enabled_yaml_check
+ feature = double('FeatureFlag', percentage_of_time_value: percentage, enabled?: true)
+ allow(Feature).to receive(:get).with(:experiment_key_experiment_percentage).and_return(feature)
end
subject(:experiment) { described_class.new(:experiment_key, **params) }
diff --git a/spec/lib/gitlab/experimentation_spec.rb b/spec/lib/gitlab/experimentation_spec.rb
index a68c050d829..b503960b8c7 100644
--- a/spec/lib/gitlab/experimentation_spec.rb
+++ b/spec/lib/gitlab/experimentation_spec.rb
@@ -38,6 +38,8 @@ RSpec.describe Gitlab::Experimentation do
}
})
+ skip_feature_flags_yaml_validation
+ skip_default_enabled_yaml_check
Feature.enable_percentage_of_time(:backwards_compatible_test_experiment_experiment_percentage, enabled_percentage)
Feature.enable_percentage_of_time(:test_experiment_experiment_percentage, enabled_percentage)
allow(Gitlab).to receive(:com?).and_return(true)
diff --git a/spec/lib/gitlab/faraday/error_callback_spec.rb b/spec/lib/gitlab/faraday/error_callback_spec.rb
new file mode 100644
index 00000000000..5da4b8adf6a
--- /dev/null
+++ b/spec/lib/gitlab/faraday/error_callback_spec.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Faraday::ErrorCallback do
+ let(:app) { double(:app) }
+ let(:middleware) { described_class.new(app, {}) }
+
+ describe '#call' do
+ let(:env) { { url: 'http://target.url' } }
+
+ subject { middleware.call(env) }
+
+ context 'with no errors' do
+ before do
+ expect(app).to receive(:call).with(env).and_return('success')
+ end
+
+ it { is_expected.to eq('success') }
+ end
+
+ context 'with errors' do
+ before do
+ expect(app).to receive(:call).and_raise(ArgumentError, 'Kaboom!')
+ end
+
+ context 'with no callback' do
+ it 'uses the default callback' do
+ expect { subject }.to raise_error(ArgumentError, 'Kaboom!')
+ end
+ end
+
+ context 'with a custom callback' do
+ let(:options) { { callback: callback } }
+
+ it 'uses the custom callback' do
+ count = 0
+ target_url = nil
+ exception_class = nil
+
+ callback = proc do |env, exception|
+ count += 1
+ target_url = env[:url].to_s
+ exception_class = exception.class.name
+ end
+
+ options = { callback: callback }
+ middleware = described_class.new(app, options)
+
+ expect(callback).to receive(:call).and_call_original
+ expect { middleware.call(env) }.to raise_error(ArgumentError, 'Kaboom!')
+ expect(count).to eq(1)
+ expect(target_url).to eq('http://target.url')
+ expect(exception_class).to eq(ArgumentError.name)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/git/changed_path_spec.rb b/spec/lib/gitlab/git/changed_path_spec.rb
new file mode 100644
index 00000000000..93db107ad5c
--- /dev/null
+++ b/spec/lib/gitlab/git/changed_path_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe Gitlab::Git::ChangedPath do
+ subject(:changed_path) { described_class.new(path: path, status: status) }
+
+ let(:path) { 'test_path' }
+
+ describe '#new_file?' do
+ subject(:new_file?) { changed_path.new_file? }
+
+ context 'when it is a new file' do
+ let(:status) { :ADDED }
+
+ it 'returns true' do
+ expect(new_file?).to eq(true)
+ end
+ end
+
+ context 'when it is not a new file' do
+ let(:status) { :MODIFIED }
+
+ it 'returns false' do
+ expect(new_file?).to eq(false)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/git/diff_spec.rb b/spec/lib/gitlab/git/diff_spec.rb
index d4174a34433..783f0a9ccf7 100644
--- a/spec/lib/gitlab/git/diff_spec.rb
+++ b/spec/lib/gitlab/git/diff_spec.rb
@@ -58,7 +58,7 @@ EOT
context 'using a diff that is too large' do
it 'prunes the diff' do
- diff = described_class.new(diff: 'a' * 204800)
+ diff = described_class.new({ diff: 'a' * 204800 })
expect(diff.diff).to be_empty
expect(diff).to be_too_large
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index c917945499c..ef9b5a30c86 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -520,12 +520,13 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
forced: true,
no_tags: true,
timeout: described_class::GITLAB_PROJECTS_TIMEOUT,
- prune: false
+ prune: false,
+ check_tags_changed: false
}
expect(repository.gitaly_repository_client).to receive(:fetch_remote).with('remote-name', expected_opts)
- repository.fetch_remote('remote-name', ssh_auth: ssh_auth, forced: true, no_tags: true, prune: false)
+ repository.fetch_remote('remote-name', ssh_auth: ssh_auth, forced: true, no_tags: true, prune: false, check_tags_changed: false)
end
it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::RepositoryService, :fetch_remote do
@@ -1191,25 +1192,25 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
let(:commit_3) { '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9' }
let(:commit_1_files) do
[
- OpenStruct.new(status: :ADDED, path: "files/executables/ls"),
- OpenStruct.new(status: :ADDED, path: "files/executables/touch"),
- OpenStruct.new(status: :ADDED, path: "files/links/regex.rb"),
- OpenStruct.new(status: :ADDED, path: "files/links/ruby-style-guide.md"),
- OpenStruct.new(status: :ADDED, path: "files/links/touch"),
- OpenStruct.new(status: :MODIFIED, path: ".gitmodules"),
- OpenStruct.new(status: :ADDED, path: "deeper/nested/six"),
- OpenStruct.new(status: :ADDED, path: "nested/six")
+ Gitlab::Git::ChangedPath.new(status: :ADDED, path: "files/executables/ls"),
+ Gitlab::Git::ChangedPath.new(status: :ADDED, path: "files/executables/touch"),
+ Gitlab::Git::ChangedPath.new(status: :ADDED, path: "files/links/regex.rb"),
+ Gitlab::Git::ChangedPath.new(status: :ADDED, path: "files/links/ruby-style-guide.md"),
+ Gitlab::Git::ChangedPath.new(status: :ADDED, path: "files/links/touch"),
+ Gitlab::Git::ChangedPath.new(status: :MODIFIED, path: ".gitmodules"),
+ Gitlab::Git::ChangedPath.new(status: :ADDED, path: "deeper/nested/six"),
+ Gitlab::Git::ChangedPath.new(status: :ADDED, path: "nested/six")
]
end
let(:commit_2_files) do
- [OpenStruct.new(status: :ADDED, path: "bin/executable")]
+ [Gitlab::Git::ChangedPath.new(status: :ADDED, path: "bin/executable")]
end
let(:commit_3_files) do
[
- OpenStruct.new(status: :MODIFIED, path: ".gitmodules"),
- OpenStruct.new(status: :ADDED, path: "gitlab-shell")
+ Gitlab::Git::ChangedPath.new(status: :MODIFIED, path: ".gitmodules"),
+ Gitlab::Git::ChangedPath.new(status: :ADDED, path: "gitlab-shell")
]
end
@@ -1217,7 +1218,7 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
collection = repository.find_changed_paths([commit_1, commit_2, commit_3])
expect(collection).to be_a(Enumerable)
- expect(collection.to_a).to eq(commit_1_files + commit_2_files + commit_3_files)
+ expect(collection.as_json).to eq((commit_1_files + commit_2_files + commit_3_files).as_json)
end
it 'returns no paths when SHAs are invalid' do
@@ -1231,7 +1232,7 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
collection = repository.find_changed_paths([nil, commit_1])
expect(collection).to be_a(Enumerable)
- expect(collection.to_a).to eq(commit_1_files)
+ expect(collection.as_json).to eq(commit_1_files.as_json)
end
it 'returns no paths when the commits are nil' do
diff --git a/spec/lib/gitlab/git/wiki_page_version_spec.rb b/spec/lib/gitlab/git/wiki_page_version_spec.rb
new file mode 100644
index 00000000000..836fa2449ec
--- /dev/null
+++ b/spec/lib/gitlab/git/wiki_page_version_spec.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Git::WikiPageVersion do
+ let_it_be(:project) { create(:project, :public, :repository) }
+ let(:user) { create(:user, username: 'someone') }
+
+ describe '#author_url' do
+ subject(:author_url) { described_class.new(commit, nil).author_url }
+
+ context 'user exists in gitlab' do
+ let(:commit) { create(:commit, project: project, author: user) }
+
+ it 'returns the profile link of the user' do
+ expect(author_url).to eq('http://localhost/someone')
+ end
+ end
+
+ context 'user does not exist in gitlab' do
+ let(:commit) { create(:commit, project: project, author_email: "someone@somewebsite.com") }
+
+ it 'returns a mailto: url' do
+ expect(author_url).to eq('mailto:someone@somewebsite.com')
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/git_access_snippet_spec.rb b/spec/lib/gitlab/git_access_snippet_spec.rb
index f5d8758a78a..777c94035d4 100644
--- a/spec/lib/gitlab/git_access_snippet_spec.rb
+++ b/spec/lib/gitlab/git_access_snippet_spec.rb
@@ -29,8 +29,17 @@ RSpec.describe Gitlab::GitAccessSnippet do
let(:actor) { build(:deploy_key) }
it 'does not allow push and pull access' do
- expect { push_access_check }.to raise_forbidden(described_class::ERROR_MESSAGES[:authentication_mechanism])
- expect { pull_access_check }.to raise_forbidden(described_class::ERROR_MESSAGES[:authentication_mechanism])
+ expect { push_access_check }.to raise_forbidden(:authentication_mechanism)
+ expect { pull_access_check }.to raise_forbidden(:authentication_mechanism)
+ end
+ end
+
+ describe 'when snippet repository is read-only' do
+ it 'does not allow push and allows pull access' do
+ allow(snippet).to receive(:repository_read_only?).and_return(true)
+
+ expect { push_access_check }.to raise_forbidden(:read_only)
+ expect { pull_access_check }.not_to raise_error
end
end
@@ -58,7 +67,7 @@ RSpec.describe Gitlab::GitAccessSnippet do
let(:snippet) { nil }
it 'blocks access with "not found"' do
- expect { pull_access_check }.to raise_snippet_not_found
+ expect { pull_access_check }.to raise_not_found(:snippet_not_found)
end
end
@@ -66,7 +75,7 @@ RSpec.describe Gitlab::GitAccessSnippet do
let(:snippet) { build_stubbed(:personal_snippet) }
it 'blocks access with "not found"' do
- expect { pull_access_check }.to raise_snippet_not_found
+ expect { pull_access_check }.to raise_not_found(:no_repo)
end
end
end
@@ -81,8 +90,8 @@ RSpec.describe Gitlab::GitAccessSnippet do
it 'blocks access when the user did not accept terms' do
message = /must accept the Terms of Service in order to perform this action/
- expect { push_access_check }.to raise_forbidden(message)
- expect { pull_access_check }.to raise_forbidden(message)
+ expect { push_access_check }.to raise_forbidden_with_message(message)
+ expect { pull_access_check }.to raise_forbidden_with_message(message)
end
it 'allows access when the user accepted the terms' do
@@ -149,8 +158,8 @@ RSpec.describe Gitlab::GitAccessSnippet do
let(:membership) { membership }
it 'respects accessibility' do
- expect { push_access_check }.to raise_snippet_not_found
- expect { pull_access_check }.to raise_snippet_not_found
+ expect { push_access_check }.to raise_not_found(:project_not_found)
+ expect { pull_access_check }.to raise_not_found(:project_not_found)
end
end
end
@@ -172,7 +181,7 @@ RSpec.describe Gitlab::GitAccessSnippet do
end
end
- [:guest, :reporter, :maintainer, :author, :admin].each do |membership|
+ [:guest, :reporter, :maintainer, :author].each do |membership|
context membership.to_s do
let(:membership) { membership }
@@ -183,6 +192,24 @@ RSpec.describe Gitlab::GitAccessSnippet do
end
end
+ context 'admin' do
+ let(:membership) { :admin }
+
+ context 'when admin mode is enabled', :enable_admin_mode do
+ it 'cannot perform git pushes' do
+ expect { push_access_check }.to raise_error(described_class::ForbiddenError)
+ expect { pull_access_check }.not_to raise_error
+ end
+ end
+
+ context 'when admin mode is disabled' do
+ it 'cannot perform git operations' do
+ expect { push_access_check }.to raise_error(described_class::ForbiddenError)
+ expect { pull_access_check }.to raise_error(described_class::ForbiddenError)
+ end
+ end
+ end
+
it_behaves_like 'actor is migration bot'
end
@@ -255,7 +282,7 @@ RSpec.describe Gitlab::GitAccessSnippet do
allow(check).to receive(:validate!).and_raise(Gitlab::GitAccess::ForbiddenError, 'foo')
end
- expect { push_access_check }.to raise_forbidden('foo')
+ expect { push_access_check }.to raise_forbidden_with_message('foo')
end
it 'sets the file count limit from Snippet class' do
@@ -372,17 +399,49 @@ RSpec.describe Gitlab::GitAccessSnippet do
end
end
+ describe 'HEAD realignment' do
+ let_it_be(:snippet) { create(:project_snippet, :private, :repository, project: project) }
+
+ shared_examples 'HEAD is updated to the snippet default branch' do
+ let(:actor) { snippet.author }
+
+ specify do
+ expect(snippet).to receive(:change_head_to_default_branch).and_call_original
+
+ subject
+ end
+
+ context 'when an error is raised' do
+ let(:actor) { nil }
+
+ it 'does not realign HEAD' do
+ expect(snippet).not_to receive(:change_head_to_default_branch).and_call_original
+
+ expect { subject }.to raise_error(described_class::ForbiddenError)
+ end
+ end
+ end
+
+ it_behaves_like 'HEAD is updated to the snippet default branch' do
+ subject { push_access_check }
+ end
+
+ it_behaves_like 'HEAD is updated to the snippet default branch' do
+ subject { pull_access_check }
+ end
+ end
+
private
- def raise_snippet_not_found
- raise_error(Gitlab::GitAccess::NotFoundError, Gitlab::GitAccess::ERROR_MESSAGES[:snippet_not_found])
+ def raise_not_found(message_key)
+ raise_error(described_class::NotFoundError, described_class.error_message(message_key))
end
- def raise_project_not_found
- raise_error(Gitlab::GitAccess::NotFoundError, Gitlab::GitAccess::ERROR_MESSAGES[:project_not_found])
+ def raise_forbidden(message_key)
+ raise_error(Gitlab::GitAccess::ForbiddenError, described_class.error_message(message_key))
end
- def raise_forbidden(message)
+ def raise_forbidden_with_message(message)
raise_error(Gitlab::GitAccess::ForbiddenError, message)
end
end
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index 780f4329bcc..a0cafe3d763 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::GitAccess do
include TermsHelper
include GitHelpers
+ include AdminModeHelper
let(:user) { create(:user) }
@@ -769,19 +770,39 @@ RSpec.describe Gitlab::GitAccess do
describe 'admin user' do
let(:user) { create(:admin) }
- context 'when member of the project' do
- before do
- project.add_reporter(user)
+ context 'when admin mode enabled', :enable_admin_mode do
+ context 'when member of the project' do
+ before do
+ project.add_reporter(user)
+ end
+
+ context 'pull code' do
+ it { expect { pull_access_check }.not_to raise_error }
+ end
end
- context 'pull code' do
- it { expect { pull_access_check }.not_to raise_error }
+ context 'when is not member of the project' do
+ context 'pull code' do
+ it { expect { pull_access_check }.to raise_forbidden(described_class::ERROR_MESSAGES[:download]) }
+ end
end
end
- context 'when is not member of the project' do
- context 'pull code' do
- it { expect { pull_access_check }.to raise_forbidden(described_class::ERROR_MESSAGES[:download]) }
+ context 'when admin mode disabled' do
+ context 'when member of the project' do
+ before do
+ project.add_reporter(user)
+ end
+
+ context 'pull code' do
+ it { expect { pull_access_check }.not_to raise_error }
+ end
+ end
+
+ context 'when is not member of the project' do
+ context 'pull code' do
+ it { expect { pull_access_check }.to raise_not_found }
+ end
end
end
end
@@ -870,8 +891,13 @@ RSpec.describe Gitlab::GitAccess do
# Expectations are given a custom failure message proc so that it's
# easier to identify which check(s) failed.
it "has the correct permissions for #{role}s" do
- if role == :admin
+ if role == :admin_without_admin_mode
+ skip("All admins are allowed to perform actions https://gitlab.com/gitlab-org/gitlab/-/issues/296509")
+ end
+
+ if [:admin_with_admin_mode, :admin_without_admin_mode].include?(role)
user.update_attribute(:admin, true)
+ enable_admin_mode!(user) if role == :admin_with_admin_mode
project.add_guest(user)
else
project.add_role(user, role)
@@ -897,7 +923,7 @@ RSpec.describe Gitlab::GitAccess do
end
permissions_matrix = {
- admin: {
+ admin_with_admin_mode: {
any: true,
push_new_branch: true,
push_master: true,
@@ -909,6 +935,18 @@ RSpec.describe Gitlab::GitAccess do
merge_into_protected_branch: true
},
+ admin_without_admin_mode: {
+ any: false,
+ push_new_branch: false,
+ push_master: false,
+ push_protected_branch: false,
+ push_remove_protected_branch: false,
+ push_tag: false,
+ push_new_tag: false,
+ push_all: false,
+ merge_into_protected_branch: false
+ },
+
maintainer: {
any: true,
push_new_branch: true,
@@ -1009,7 +1047,7 @@ RSpec.describe Gitlab::GitAccess do
run_permission_checks(permissions_matrix.deep_merge(developer: { push_protected_branch: false, push_all: false, merge_into_protected_branch: false },
maintainer: { push_protected_branch: false, push_all: false, merge_into_protected_branch: false },
- admin: { push_protected_branch: false, push_all: false, merge_into_protected_branch: false }))
+ admin_with_admin_mode: { push_protected_branch: false, push_all: false, merge_into_protected_branch: false }))
end
end
diff --git a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
index 157c2393ce1..ac4c42d57ee 100644
--- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
@@ -162,11 +162,9 @@ RSpec.describe Gitlab::GitalyClient::CommitService do
.with(request, kind_of(Hash)).and_return([changed_paths_response])
returned_value = described_class.new(repository).find_changed_paths(commits)
+ mapped_expected_value = changed_paths_response.paths.map { |path| Gitlab::Git::ChangedPath.new(status: path.status, path: path.path) }
- mapped_returned_value = returned_value.map(&:to_h)
- mapped_expected_value = changed_paths_response.paths.map(&:to_h)
-
- expect(mapped_returned_value).to eq(mapped_expected_value)
+ expect(returned_value.as_json).to eq(mapped_expected_value.as_json)
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 f810a5c15a5..7a382df1248 100644
--- a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
@@ -131,7 +131,8 @@ RSpec.describe Gitlab::GitalyClient::RepositoryService do
known_hosts: '',
force: false,
no_tags: false,
- no_prune: false
+ no_prune: false,
+ check_tags_changed: false
)
expect_any_instance_of(Gitaly::RepositoryService::Stub)
@@ -139,7 +140,7 @@ RSpec.describe Gitlab::GitalyClient::RepositoryService do
.with(expected_request, kind_of(Hash))
.and_return(double(value: true))
- client.fetch_remote(remote, ssh_auth: nil, forced: false, no_tags: false, timeout: 1)
+ client.fetch_remote(remote, ssh_auth: nil, forced: false, no_tags: false, timeout: 1, check_tags_changed: false)
end
context 'SSH auth' do
diff --git a/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb b/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb
index 180c6d9e420..3839303b881 100644
--- a/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb
@@ -205,7 +205,7 @@ RSpec.describe Gitlab::GithubImport::Importer::RepositoryImporter do
.with(project.import_url, refmap: Gitlab::GithubImport.refmap, forced: true, remote_name: 'github')
service = double
- expect(Projects::HousekeepingService)
+ expect(Repositories::HousekeepingService)
.to receive(:new).with(project, :gc).and_return(service)
expect(service).to receive(:execute)
diff --git a/spec/lib/gitlab/gitpod_spec.rb b/spec/lib/gitlab/gitpod_spec.rb
deleted file mode 100644
index 717e396f942..00000000000
--- a/spec/lib/gitlab/gitpod_spec.rb
+++ /dev/null
@@ -1,73 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Gitpod do
- let_it_be(:user) { create(:user) }
-
- before do
- stub_feature_flags(gitpod: feature_scope)
- end
-
- describe '.feature_available?' do
- subject { described_class.feature_available? }
-
- context 'when feature has not been set' do
- let(:feature_scope) { nil }
-
- it { is_expected.to be_truthy }
- end
-
- context 'when feature is disabled' do
- let(:feature_scope) { false }
-
- it { is_expected.to be_falsey }
- end
-
- context 'when feature is enabled globally' do
- let(:feature_scope) { true }
-
- it { is_expected.to be_truthy }
- end
-
- context 'when feature is enabled only to a resource' do
- let(:feature_scope) { user }
-
- it { is_expected.to be_truthy }
- end
- end
-
- describe '.feature_enabled?' do
- let(:current_user) { nil }
-
- subject { described_class.feature_enabled?(current_user) }
-
- context 'when feature has not been set' do
- let(:feature_scope) { nil }
-
- it { is_expected.to be_truthy }
- end
-
- context 'when feature is enabled globally' do
- let(:feature_scope) { true }
-
- it { is_expected.to be_truthy }
- end
-
- context 'when feature is enabled only to a resource' do
- let(:feature_scope) { user }
-
- context 'for the same resource' do
- let(:current_user) { user }
-
- it { is_expected.to be_truthy }
- end
-
- context 'for a different resource' do
- let(:current_user) { create(:user) }
-
- it { is_expected.to be_falsey }
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/graphql/batch_key_spec.rb b/spec/lib/gitlab/graphql/batch_key_spec.rb
new file mode 100644
index 00000000000..881fba5c1be
--- /dev/null
+++ b/spec/lib/gitlab/graphql/batch_key_spec.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+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) }
+
+ subject { described_class.new(object, lookahead, object_name: :rect) }
+
+ it 'is equal to keys of the same object, regardless of lookahead or object name' do
+ expect(subject).to eq(described_class.new(rect.new(2, 3)))
+ expect(subject).to eq(described_class.new(rect.new(2, 3), :anything))
+ expect(subject).to eq(described_class.new(rect.new(2, 3), lookahead, object_name: :does_not_matter))
+ expect(subject).not_to eq(described_class.new(rect.new(2, 4)))
+ expect(subject).not_to eq(described_class.new(circle.new(10)))
+ end
+
+ it 'delegates attribute lookup methods to the inner object' do
+ other = rect.new(2, 3)
+
+ expect(subject.hash).to eq(other.hash)
+ expect(subject.len).to eq(other.len)
+ expect(subject.width).to eq(other.width)
+ end
+
+ it 'allows the object to be named more meaningfully' do
+ expect(subject.object).to eq(object)
+ expect(subject.object).to eq(subject.rect)
+ end
+
+ it 'works as a hash key' do
+ h = { subject => :foo }
+
+ expect(h[described_class.new(object)]).to eq(:foo)
+ end
+
+ describe '#requires?' do
+ it 'returns false if the lookahead was not provided' do
+ expect(subject.requires?([:foo])).to be(false)
+ end
+
+ context 'lookahead was provided' do
+ let(:lookahead) { double(:Lookahead) }
+
+ before do
+ allow(lookahead).to receive(:selection).with(Symbol).and_return(lookahead)
+ end
+
+ it 'returns false if the path is empty' do
+ expect(subject.requires?([])).to be(false)
+ end
+
+ context 'it selects the field' do
+ before do
+ allow(lookahead).to receive(:selects?).with(Symbol).once.and_return(true)
+ end
+
+ it 'returns true' do
+ expect(subject.requires?(%i[foo bar baz])).to be(true)
+ end
+ end
+
+ context 'it does not select the field' do
+ before do
+ allow(lookahead).to receive(:selects?).with(Symbol).once.and_return(false)
+ end
+
+ it 'returns false' do
+ expect(subject.requires?(%i[foo bar baz])).to be(false)
+ end
+ end
+ 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 0ac54a20fcc..02e67488d3f 100644
--- a/spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb
+++ b/spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb
@@ -21,6 +21,47 @@ RSpec.describe Gitlab::Graphql::Pagination::Keyset::Connection do
Gitlab::Json.parse(Base64Bp.urlsafe_decode64(cursor))
end
+ # see: https://gitlab.com/gitlab-org/gitlab/-/issues/297358
+ context 'the relation has been preloaded' do
+ let(:projects) { Project.all.preload(:issues) }
+ let(:nodes) { projects.first.issues }
+
+ before do
+ project = create(:project)
+ create_list(:issue, 3, project: project)
+ end
+
+ it 'is loaded' do
+ expect(nodes).to be_loaded
+ end
+
+ it 'does not error when accessing pagination information' do
+ connection.first = 2
+
+ expect(connection).to have_attributes(
+ has_previous_page: false,
+ has_next_page: true
+ )
+ end
+
+ it 'can generate cursors' do
+ connection.send(:ordered_items) # necessary to generate the order-list
+
+ expect(connection.cursor_for(nodes.first)).to be_a(String)
+ end
+
+ it 'can read the next page' do
+ connection.send(:ordered_items) # necessary to generate the order-list
+ ordered = nodes.reorder(id: :desc)
+ next_page = described_class.new(nodes,
+ context: context,
+ max_page_size: 3,
+ after: connection.cursor_for(ordered.second))
+
+ expect(next_page.sliced_nodes).to contain_exactly(ordered.third)
+ end
+ end
+
it_behaves_like 'a connection with collection methods'
it_behaves_like 'a redactable connection' do
diff --git a/spec/lib/gitlab/graphql/queries_spec.rb b/spec/lib/gitlab/graphql/queries_spec.rb
new file mode 100644
index 00000000000..6e08a87523f
--- /dev/null
+++ b/spec/lib/gitlab/graphql/queries_spec.rb
@@ -0,0 +1,343 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require "test_prof/recipes/rspec/let_it_be"
+
+RSpec.describe Gitlab::Graphql::Queries do
+ shared_examples 'a valid GraphQL query for the blog schema' do
+ it 'is valid' do
+ expect(subject.validate(schema).second).to be_empty
+ end
+ end
+
+ shared_examples 'an invalid GraphQL query for the blog schema' do
+ it 'is invalid' do
+ expect(subject.validate(schema).second).to match errors
+ end
+ end
+
+ # Toy schema to validate queries against
+ let_it_be(:schema) do
+ author = Class.new(GraphQL::Schema::Object) do
+ graphql_name 'Author'
+ field :name, GraphQL::STRING_TYPE, null: true
+ field :handle, GraphQL::STRING_TYPE, null: false
+ field :verified, GraphQL::BOOLEAN_TYPE, null: false
+ end
+
+ post = Class.new(GraphQL::Schema::Object) do
+ graphql_name 'Post'
+ field :name, GraphQL::STRING_TYPE, null: false
+ field :title, GraphQL::STRING_TYPE, null: false
+ field :content, GraphQL::STRING_TYPE, null: true
+ field :author, author, null: false
+ end
+ author.field :posts, [post], null: false do
+ argument :blog_title, GraphQL::STRING_TYPE, required: false
+ end
+
+ blog = Class.new(GraphQL::Schema::Object) do
+ graphql_name 'Blog'
+ field :title, GraphQL::STRING_TYPE, null: false
+ field :description, GraphQL::STRING_TYPE, null: false
+ field :main_author, author, null: false
+ field :posts, [post], null: false
+ field :post, post, null: true do
+ argument :slug, GraphQL::STRING_TYPE, required: true
+ end
+ end
+
+ Class.new(GraphQL::Schema) do
+ query(Class.new(GraphQL::Schema::Object) do
+ graphql_name 'Query'
+ field :blog, blog, null: true do
+ argument :title, GraphQL::STRING_TYPE, required: true
+ end
+ field :post, post, null: true do
+ argument :slug, GraphQL::STRING_TYPE, required: true
+ end
+ end)
+ end
+ end
+
+ let(:root) do
+ Rails.root / 'fixtures/lib/gitlab/graphql/queries'
+ end
+
+ describe Gitlab::Graphql::Queries::Fragments do
+ subject { described_class.new(root) }
+
+ it 'has the right home' do
+ expect(subject.home).to eq (root / 'app/assets/javascripts').to_s
+ end
+
+ it 'has the right EE home' do
+ expect(subject.home_ee).to eq (root / 'ee/app/assets/javascripts').to_s
+ end
+
+ it 'caches query definitions' do
+ fragment = subject.get('foo')
+
+ expect(fragment).to be_a(::Gitlab::Graphql::Queries::Definition)
+ expect(subject.get('foo')).to be fragment
+ end
+ end
+
+ describe '.all' do
+ it 'is the combination of finding queries in CE and EE' do
+ expect(described_class)
+ .to receive(:find).with(Rails.root / 'app/assets/javascripts').and_return([:ce])
+ expect(described_class)
+ .to receive(:find).with(Rails.root / 'ee/app/assets/javascripts').and_return([:ee])
+
+ expect(described_class.all).to eq([:ce, :ee])
+ end
+ end
+
+ describe '.find' do
+ def definition_of(path)
+ be_a(::Gitlab::Graphql::Queries::Definition)
+ .and(have_attributes(file: path.to_s))
+ end
+
+ it 'find a single specific file' do
+ path = root / 'post_by_slug.graphql'
+
+ expect(described_class.find(path)).to contain_exactly(definition_of(path))
+ end
+
+ it 'ignores files that do not exist' do
+ path = root / 'not_there.graphql'
+
+ expect(described_class.find(path)).to be_empty
+ end
+
+ it 'ignores fragments' do
+ path = root / 'author.fragment.graphql'
+
+ expect(described_class.find(path)).to be_empty
+ end
+
+ it 'ignores typedefs' do
+ path = root / 'typedefs.graphql'
+
+ expect(described_class.find(path)).to be_empty
+ end
+
+ it 'finds all query definitions under a root directory' do
+ found = described_class.find(root)
+
+ expect(found).to include(
+ definition_of(root / 'post_by_slug.graphql'),
+ definition_of(root / 'post_by_slug.with_import.graphql'),
+ definition_of(root / 'post_by_slug.with_import.misspelled.graphql'),
+ definition_of(root / 'duplicate_imports.graphql'),
+ definition_of(root / 'deeply/nested/query.graphql')
+ )
+
+ expect(found).not_to include(
+ definition_of(root / 'typedefs.graphql'),
+ definition_of(root / 'author.fragment.graphql')
+ )
+ end
+ end
+
+ describe Gitlab::Graphql::Queries::Definition do
+ let(:fragments) { Gitlab::Graphql::Queries::Fragments.new(root, '.') }
+
+ subject { described_class.new(root / path, fragments) }
+
+ context 'a simple query' do
+ let(:path) { 'post_by_slug.graphql' }
+
+ it_behaves_like 'a valid GraphQL query for the blog schema'
+ end
+
+ context 'a query with an import' do
+ let(:path) { 'post_by_slug.with_import.graphql' }
+
+ it_behaves_like 'a valid GraphQL query for the blog schema'
+ end
+
+ context 'a query with duplicate imports' do
+ let(:path) { 'duplicate_imports.graphql' }
+
+ it_behaves_like 'a valid GraphQL query for the blog schema'
+ end
+
+ context 'a query importing from ee_else_ce' do
+ let(:path) { 'ee_else_ce.import.graphql' }
+
+ it_behaves_like 'a valid GraphQL query for the blog schema'
+
+ it 'can resolve the ee fields' do
+ expect(subject.text(mode: :ce)).not_to include('verified')
+ expect(subject.text(mode: :ee)).to include('verified')
+ end
+ end
+
+ context 'a query refering to parent directories' do
+ let(:path) { 'deeply/nested/query.graphql' }
+
+ it_behaves_like 'a valid GraphQL query for the blog schema'
+ end
+
+ context 'a query refering to parent directories, incorrectly' do
+ let(:path) { 'deeply/nested/bad_import.graphql' }
+
+ it_behaves_like 'an invalid GraphQL query for the blog schema' do
+ let(:errors) do
+ contain_exactly(
+ be_a(::Gitlab::Graphql::Queries::FileNotFound)
+ .and(have_attributes(message: include('deeply/author.fragment.graphql')))
+ )
+ end
+ end
+ end
+
+ context 'a query with a broken import' do
+ let(:path) { 'post_by_slug.with_import.misspelled.graphql' }
+
+ it_behaves_like 'an invalid GraphQL query for the blog schema' do
+ let(:errors) do
+ contain_exactly(
+ be_a(::Gitlab::Graphql::Queries::FileNotFound)
+ .and(have_attributes(message: include('auther.fragment.graphql')))
+ )
+ end
+ end
+ end
+
+ context 'a query which imports a file with a broken import' do
+ let(:path) { 'transitive_bad_import.graphql' }
+
+ it_behaves_like 'an invalid GraphQL query for the blog schema' do
+ let(:errors) do
+ contain_exactly(
+ be_a(::Gitlab::Graphql::Queries::FileNotFound)
+ .and(have_attributes(message: include('does-not-exist.graphql')))
+ )
+ end
+ end
+ end
+
+ context 'a query containing a client directive' do
+ let(:path) { 'client.query.graphql' }
+
+ it_behaves_like 'a valid GraphQL query for the blog schema'
+
+ it 'is tagged as a client query' do
+ expect(subject.validate(schema).first).to eq :client_query
+ end
+ end
+
+ context 'a mixed client query, valid' do
+ let(:path) { 'mixed_client.query.graphql' }
+
+ it_behaves_like 'a valid GraphQL query for the blog schema'
+
+ it 'is not tagged as a client query' do
+ expect(subject.validate(schema).first).not_to eq :client_query
+ end
+ end
+
+ context 'a mixed client query, with skipped argument' do
+ let(:path) { 'mixed_client_skipped_argument.graphql' }
+
+ it_behaves_like 'a valid GraphQL query for the blog schema'
+ end
+
+ context 'a mixed client query, with unused fragment' do
+ let(:path) { 'mixed_client_unused_fragment.graphql' }
+
+ it_behaves_like 'a valid GraphQL query for the blog schema'
+ end
+
+ context 'a client query, with unused fragment' do
+ let(:path) { 'client_unused_fragment.graphql' }
+
+ it_behaves_like 'a valid GraphQL query for the blog schema'
+
+ it 'is tagged as a client query' do
+ expect(subject.validate(schema).first).to eq :client_query
+ end
+ end
+
+ context 'a mixed client query, invalid' do
+ let(:path) { 'mixed_client_invalid.query.graphql' }
+
+ it_behaves_like 'an invalid GraphQL query for the blog schema' do
+ let(:errors) do
+ contain_exactly(have_attributes(message: include('titlz')))
+ end
+ end
+ end
+
+ context 'a query containing a connection directive' do
+ let(:path) { 'connection.query.graphql' }
+
+ it_behaves_like 'a valid GraphQL query for the blog schema'
+ end
+
+ context 'a query which mentions an incorrect field' do
+ let(:path) { 'wrong_field.graphql' }
+
+ it_behaves_like 'an invalid GraphQL query for the blog schema' do
+ let(:errors) do
+ contain_exactly(
+ have_attributes(message: /'createdAt' doesn't exist/),
+ have_attributes(message: /'categories' doesn't exist/)
+ )
+ end
+ end
+ end
+
+ context 'a query which has a missing argument' do
+ let(:path) { 'missing_argument.graphql' }
+
+ it_behaves_like 'an invalid GraphQL query for the blog schema' do
+ let(:errors) do
+ contain_exactly(
+ have_attributes(message: include('blog'))
+ )
+ end
+ end
+ end
+
+ context 'a query which has a bad argument' do
+ let(:path) { 'bad_argument.graphql' }
+
+ it_behaves_like 'an invalid GraphQL query for the blog schema' do
+ let(:errors) do
+ contain_exactly(
+ have_attributes(message: include('Nullability mismatch on variable $bad'))
+ )
+ end
+ end
+ end
+
+ context 'a query which has a syntax error' do
+ let(:path) { 'syntax-error.graphql' }
+
+ it_behaves_like 'an invalid GraphQL query for the blog schema' do
+ let(:errors) do
+ contain_exactly(
+ have_attributes(message: include('Parse error'))
+ )
+ end
+ end
+ end
+
+ context 'a query which has an unused import' do
+ let(:path) { 'unused_import.graphql' }
+
+ it_behaves_like 'an invalid GraphQL query for the blog schema' do
+ let(:errors) do
+ contain_exactly(
+ have_attributes(message: include('AuthorF was defined, but not used'))
+ )
+ 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 fba32ae0673..825513bdfc5 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -87,6 +87,7 @@ label:
- merge_requests
- priorities
- epic_board_labels
+- epic_lists
milestone:
- group
- project
@@ -524,7 +525,6 @@ project:
- designs
- project_aliases
- external_pull_requests
-- alerts_service
- grafana_integration
- remove_source_branch_after_merge
- deleting_user
@@ -560,6 +560,7 @@ project:
- alert_management_http_integrations
- exported_protected_branches
- incident_management_oncall_schedules
+- debian_distributions
award_emoji:
- awardable
- user
@@ -722,6 +723,7 @@ epic:
- user_mentions
- note_authors
- boards_epic_user_preferences
+- epic_board_positions
epic_issue:
- epic
- issue
diff --git a/spec/lib/gitlab/kubernetes/cilium_network_policy_spec.rb b/spec/lib/gitlab/kubernetes/cilium_network_policy_spec.rb
index 3f5661d4ca6..0092c69d0bb 100644
--- a/spec/lib/gitlab/kubernetes/cilium_network_policy_spec.rb
+++ b/spec/lib/gitlab/kubernetes/cilium_network_policy_spec.rb
@@ -12,7 +12,8 @@ RSpec.describe Gitlab::Kubernetes::CiliumNetworkPolicy do
ingress: ingress,
egress: egress,
labels: labels,
- resource_version: resource_version
+ resource_version: resource_version,
+ annotations: annotations
)
end
@@ -20,7 +21,7 @@ RSpec.describe Gitlab::Kubernetes::CiliumNetworkPolicy do
::Kubeclient::Resource.new(
apiVersion: Gitlab::Kubernetes::CiliumNetworkPolicy::API_VERSION,
kind: Gitlab::Kubernetes::CiliumNetworkPolicy::KIND,
- metadata: { name: name, namespace: namespace, resourceVersion: resource_version },
+ metadata: { name: name, namespace: namespace, resourceVersion: resource_version, annotations: annotations },
spec: { endpointSelector: endpoint_selector, ingress: ingress, egress: egress },
description: description
)
@@ -34,6 +35,7 @@ RSpec.describe Gitlab::Kubernetes::CiliumNetworkPolicy do
let(:description) { 'example-description' }
let(:partial_class_name) { described_class.name.split('::').last }
let(:resource_version) { 101 }
+ let(:annotations) { { 'app.gitlab.com/alert': 'true' } }
let(:ingress) do
[
{
@@ -64,6 +66,8 @@ RSpec.describe Gitlab::Kubernetes::CiliumNetworkPolicy do
name: example-name
namespace: example-namespace
resourceVersion: 101
+ annotations:
+ app.gitlab.com/alert: "true"
spec:
endpointSelector:
matchLabels:
@@ -157,7 +161,7 @@ RSpec.describe Gitlab::Kubernetes::CiliumNetworkPolicy do
description: description,
metadata: {
name: name, namespace: namespace, creationTimestamp: '2020-04-14T00:08:30Z',
- labels: { app: 'foo' }, resourceVersion: resource_version
+ labels: { app: 'foo' }, resourceVersion: resource_version, annotations: annotations
},
spec: { endpointSelector: endpoint_selector, ingress: ingress, egress: nil, labels: nil }
)
@@ -168,7 +172,7 @@ RSpec.describe Gitlab::Kubernetes::CiliumNetworkPolicy do
apiVersion: Gitlab::Kubernetes::CiliumNetworkPolicy::API_VERSION,
kind: Gitlab::Kubernetes::CiliumNetworkPolicy::KIND,
description: description,
- metadata: { name: name, namespace: namespace, resourceVersion: resource_version, labels: { app: 'foo' } },
+ metadata: { name: name, namespace: namespace, resourceVersion: resource_version, labels: { app: 'foo' }, annotations: annotations },
spec: { endpointSelector: endpoint_selector, ingress: ingress }
)
end
@@ -211,7 +215,7 @@ RSpec.describe Gitlab::Kubernetes::CiliumNetworkPolicy do
{
apiVersion: Gitlab::Kubernetes::CiliumNetworkPolicy::API_VERSION,
kind: Gitlab::Kubernetes::CiliumNetworkPolicy::KIND,
- metadata: { name: name, namespace: namespace, resourceVersion: resource_version },
+ metadata: { name: name, namespace: namespace, resourceVersion: resource_version, annotations: annotations },
spec: { endpointSelector: endpoint_selector, ingress: ingress, egress: egress },
description: description
}
@@ -248,5 +252,15 @@ RSpec.describe Gitlab::Kubernetes::CiliumNetworkPolicy do
it { is_expected.to eq(resource) }
end
+
+ context 'without annotations' do
+ let(:annotations) { nil }
+
+ before do
+ resource[:metadata].delete(:annotations)
+ end
+
+ it { is_expected.to eq(resource) }
+ end
end
end
diff --git a/spec/lib/gitlab/kubernetes/kubectl_cmd_spec.rb b/spec/lib/gitlab/kubernetes/kubectl_cmd_spec.rb
index e80bb3dfb07..2e373613269 100644
--- a/spec/lib/gitlab/kubernetes/kubectl_cmd_spec.rb
+++ b/spec/lib/gitlab/kubernetes/kubectl_cmd_spec.rb
@@ -56,9 +56,10 @@ RSpec.describe Gitlab::Kubernetes::KubectlCmd do
describe '.delete_crds_from_group' do
it 'constructs string properly' do
- expected_command = 'kubectl api-resources -o name --api-group foo | xargs kubectl delete --ignore-not-found crd'
+ command = 'kubectl api-resources -o name --api-group foo | xargs -r kubectl delete --ignore-not-found crd'
+ command_with_retries = "for i in $(seq 1 3); do #{command} && s=0 && break || s=$?; sleep 1s; echo \"Retrying ($i)...\"; done; (exit $s)"
- expect(described_class.delete_crds_from_group("foo")).to eq expected_command
+ expect(described_class.delete_crds_from_group("foo")).to eq command_with_retries
end
end
end
diff --git a/spec/lib/gitlab/kubernetes/pod_cmd_spec.rb b/spec/lib/gitlab/kubernetes/pod_cmd_spec.rb
new file mode 100644
index 00000000000..51bdbf64741
--- /dev/null
+++ b/spec/lib/gitlab/kubernetes/pod_cmd_spec.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+RSpec.describe Gitlab::Kubernetes::PodCmd do
+ describe '.retry_command' do
+ it 'constructs string properly' do
+ command = 'my command'
+ command_with_retries = "for i in $(seq 1 3); do #{command} && s=0 && break || s=$?; sleep 1s; echo \"Retrying ($i)...\"; done; (exit $s)"
+
+ expect(described_class.retry_command(command)).to eq command_with_retries
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/samplers/action_cable_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/action_cable_sampler_spec.rb
index 7f05f35c941..f751416f4ec 100644
--- a/spec/lib/gitlab/metrics/samplers/action_cable_sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/samplers/action_cable_sampler_spec.rb
@@ -7,15 +7,7 @@ RSpec.describe Gitlab::Metrics::Samplers::ActionCableSampler do
subject { described_class.new(action_cable: action_cable) }
- describe '#interval' do
- it 'samples every five seconds by default' do
- expect(subject.interval).to eq(5)
- end
-
- it 'samples at other intervals if requested' do
- expect(described_class.new(11).interval).to eq(11)
- end
- end
+ it_behaves_like 'metrics sampler', 'ACTION_CABLE_SAMPLER'
describe '#sample' do
let(:pool) { instance_double(Concurrent::ThreadPoolExecutor) }
diff --git a/spec/lib/gitlab/metrics/samplers/database_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/database_sampler_spec.rb
index b94d19ff227..9572e9f50be 100644
--- a/spec/lib/gitlab/metrics/samplers/database_sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/samplers/database_sampler_spec.rb
@@ -5,15 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Metrics::Samplers::DatabaseSampler do
subject { described_class.new }
- describe '#interval' do
- it 'samples every five seconds by default' do
- expect(subject.interval).to eq(5)
- end
-
- it 'samples at other intervals if requested' do
- expect(described_class.new(11).interval).to eq(11)
- end
- end
+ it_behaves_like 'metrics sampler', 'DATABASE_SAMPLER'
describe '#sample' do
before do
diff --git a/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb
index 214649d3e7e..2013435a074 100644
--- a/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb
@@ -11,15 +11,7 @@ RSpec.describe Gitlab::Metrics::Samplers::PumaSampler do
allow(Gitlab::Metrics::NullMetric).to receive(:instance).and_return(null_metric)
end
- describe '#interval' do
- it 'samples every five seconds by default' do
- expect(subject.interval).to eq(5)
- end
-
- it 'samples at other intervals if requested' do
- expect(described_class.new(11).interval).to eq(11)
- end
- end
+ it_behaves_like 'metrics sampler', 'PUMA_SAMPLER'
describe '#sample' do
before do
diff --git a/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
index eb6c83096b9..6f1e0480197 100644
--- a/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
@@ -10,6 +10,8 @@ RSpec.describe Gitlab::Metrics::Samplers::RubySampler do
allow(Gitlab::Metrics::NullMetric).to receive(:instance).and_return(null_metric)
end
+ it_behaves_like 'metrics sampler', 'RUBY_SAMPLER'
+
describe '#initialize' do
it 'sets process_start_time_seconds' do
freeze_time do
@@ -18,16 +20,6 @@ RSpec.describe Gitlab::Metrics::Samplers::RubySampler do
end
end
- describe '#interval' do
- it 'samples every sixty seconds by default' do
- expect(subject.interval).to eq(60)
- end
-
- it 'samples at other intervals if requested' do
- expect(described_class.new(11).interval).to eq(11)
- end
- end
-
describe '#sample' do
it 'adds a metric containing the process resident memory bytes' do
expect(Gitlab::Metrics::System).to receive(:memory_usage_rss).and_return(9000)
diff --git a/spec/lib/gitlab/metrics/samplers/threads_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/threads_sampler_spec.rb
index 19477589289..5dabafb7c0b 100644
--- a/spec/lib/gitlab/metrics/samplers/threads_sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/samplers/threads_sampler_spec.rb
@@ -5,15 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Metrics::Samplers::ThreadsSampler do
subject { described_class.new }
- describe '#interval' do
- it 'samples every five seconds by default' do
- expect(subject.interval).to eq(5)
- end
-
- it 'samples at other intervals if requested' do
- expect(described_class.new(11).interval).to eq(11)
- end
- end
+ it_behaves_like 'metrics sampler', 'THREADS_SAMPLER'
describe '#sample' do
before do
diff --git a/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb
index 9f2180c4170..7971a7cabd5 100644
--- a/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb
@@ -5,6 +5,8 @@ require 'spec_helper'
RSpec.describe Gitlab::Metrics::Samplers::UnicornSampler do
subject { described_class.new(1.second) }
+ it_behaves_like 'metrics sampler', 'UNICORN_SAMPLER'
+
describe '#sample' do
let(:unicorn) { Module.new }
let(:raindrops) { double('raindrops') }
diff --git a/spec/lib/gitlab/metrics/system_spec.rb b/spec/lib/gitlab/metrics/system_spec.rb
index 720bd5d79b3..732aa553737 100644
--- a/spec/lib/gitlab/metrics/system_spec.rb
+++ b/spec/lib/gitlab/metrics/system_spec.rb
@@ -96,6 +96,25 @@ RSpec.describe Gitlab::Metrics::System do
expect(described_class.memory_usage_uss_pss).to eq(uss: 475136, pss: 515072)
end
end
+
+ describe '.summary' do
+ it 'contains a selection of the available fields' do
+ stub_const('RUBY_DESCRIPTION', 'ruby-3.0-patch1')
+ mock_existing_proc_file('/proc/self/status', proc_status)
+ mock_existing_proc_file('/proc/self/smaps_rollup', proc_smaps_rollup)
+
+ summary = described_class.summary
+
+ expect(summary[:version]).to eq('ruby-3.0-patch1')
+ expect(summary[:gc_stat].keys).to eq(GC.stat.keys)
+ expect(summary[:memory_rss]).to eq(2527232)
+ expect(summary[:memory_uss]).to eq(475136)
+ expect(summary[:memory_pss]).to eq(515072)
+ expect(summary[:time_cputime]).to be_a(Float)
+ expect(summary[:time_realtime]).to be_a(Float)
+ expect(summary[:time_monotonic]).to be_a(Float)
+ end
+ end
end
context 'when /proc files do not exist' do
@@ -128,6 +147,21 @@ RSpec.describe Gitlab::Metrics::System do
expect(described_class.max_open_file_descriptors).to eq(0)
end
end
+
+ describe '.summary' do
+ it 'returns only available fields' do
+ summary = described_class.summary
+
+ expect(summary[:version]).to be_a(String)
+ expect(summary[:gc_stat].keys).to eq(GC.stat.keys)
+ expect(summary[:memory_rss]).to eq(0)
+ expect(summary[:memory_uss]).to eq(0)
+ expect(summary[:memory_pss]).to eq(0)
+ expect(summary[:time_cputime]).to be_a(Float)
+ expect(summary[:time_realtime]).to be_a(Float)
+ expect(summary[:time_monotonic]).to be_a(Float)
+ end
+ end
end
describe '.cpu_time' do
diff --git a/spec/lib/gitlab/middleware/multipart/handler_for_jwt_params_spec.rb b/spec/lib/gitlab/middleware/multipart/handler_for_jwt_params_spec.rb
deleted file mode 100644
index 59ec743f6ca..00000000000
--- a/spec/lib/gitlab/middleware/multipart/handler_for_jwt_params_spec.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Middleware::Multipart::HandlerForJWTParams do
- using RSpec::Parameterized::TableSyntax
-
- let_it_be(:env) { Rack::MockRequest.env_for('/', method: 'post', params: {}) }
- let_it_be(:message) { { 'rewritten_fields' => {} } }
-
- describe '#allowed_paths' do
- let_it_be(:expected_allowed_paths) do
- [
- Dir.tmpdir,
- ::FileUploader.root,
- ::Gitlab.config.uploads.storage_path,
- ::JobArtifactUploader.workhorse_upload_path,
- ::LfsObjectUploader.workhorse_upload_path,
- File.join(Rails.root, 'public/uploads/tmp')
- ]
- end
-
- let_it_be(:expected_with_packages_path) { expected_allowed_paths + [::Packages::PackageFileUploader.workhorse_upload_path] }
-
- subject { described_class.new(env, message).send(:allowed_paths) }
-
- where(:package_features_enabled, :object_storage_enabled, :direct_upload_enabled, :expected_paths) do
- false | false | true | :expected_allowed_paths
- false | false | false | :expected_allowed_paths
- false | true | true | :expected_allowed_paths
- false | true | false | :expected_allowed_paths
- true | false | true | :expected_with_packages_path
- true | false | false | :expected_with_packages_path
- true | true | true | :expected_allowed_paths
- true | true | false | :expected_with_packages_path
- end
-
- with_them do
- before do
- stub_config(packages: {
- enabled: package_features_enabled,
- object_store: {
- enabled: object_storage_enabled,
- direct_upload: direct_upload_enabled
- },
- storage_path: '/any/dir'
- })
- end
-
- it { is_expected.to eq(send(expected_paths)) }
- end
- end
-end
diff --git a/spec/lib/gitlab/middleware/multipart_with_handler_for_jwt_params_spec.rb b/spec/lib/gitlab/middleware/multipart_spec.rb
index a1e9ac6e425..65ec3535271 100644
--- a/spec/lib/gitlab/middleware/multipart_with_handler_for_jwt_params_spec.rb
+++ b/spec/lib/gitlab/middleware/multipart_spec.rb
@@ -21,10 +21,6 @@ RSpec.describe Gitlab::Middleware::Multipart do
middleware.call(env)
end
- before do
- stub_feature_flags(upload_middleware_jwt_params_handler: true)
- end
-
context 'remote file mode' do
let(:mode) { :remote }
@@ -34,7 +30,7 @@ RSpec.describe Gitlab::Middleware::Multipart do
include_context 'with one temporary file for multipart'
let(:rewritten_fields) { rewritten_fields_hash('file' => uploaded_filepath) }
- let(:params) { upload_parameters_for(key: 'file', filename: filename, remote_id: remote_id).merge('file.path' => '/should/not/be/read') }
+ let(:params) { upload_parameters_for(key: 'file', mode: mode, filename: filename, remote_id: remote_id).merge('file.path' => '/should/not/be/read') }
it 'builds an UploadedFile' do
expect_uploaded_files(original_filename: filename, remote_id: remote_id, size: uploaded_file.size, params_path: %w(file))
@@ -55,14 +51,14 @@ RSpec.describe Gitlab::Middleware::Multipart do
let(:allowed_paths) { [Dir.tmpdir] }
before do
- expect_next_instance_of(::Gitlab::Middleware::Multipart::HandlerForJWTParams) do |handler|
+ expect_next_instance_of(::Gitlab::Middleware::Multipart::Handler) do |handler|
expect(handler).to receive(:allowed_paths).and_return(allowed_paths)
end
end
context 'in allowed paths' do
let(:rewritten_fields) { rewritten_fields_hash('file' => uploaded_filepath) }
- let(:params) { upload_parameters_for(filepath: uploaded_filepath, key: 'file', filename: filename) }
+ let(:params) { upload_parameters_for(filepath: uploaded_filepath, key: 'file', mode: mode, filename: filename) }
it 'builds an UploadedFile' do
expect_uploaded_files(filepath: uploaded_filepath, original_filename: filename, size: uploaded_file.size, params_path: %w(file))
@@ -75,7 +71,7 @@ RSpec.describe Gitlab::Middleware::Multipart do
let(:allowed_paths) { [] }
let(:rewritten_fields) { rewritten_fields_hash('file' => uploaded_filepath) }
- let(:params) { upload_parameters_for(filepath: uploaded_filepath, key: 'file') }
+ let(:params) { upload_parameters_for(filepath: uploaded_filepath, key: 'file', mode: mode) }
it 'returns an error' do
result = subject
@@ -89,7 +85,7 @@ RSpec.describe Gitlab::Middleware::Multipart do
context 'with dummy params in remote mode' do
let(:rewritten_fields) { { 'file' => 'should/not/be/read' } }
- let(:params) { upload_parameters_for(key: 'file') }
+ let(:params) { upload_parameters_for(key: 'file', mode: mode) }
let(:mode) { :remote }
context 'with an invalid secret' do
@@ -128,7 +124,7 @@ RSpec.describe Gitlab::Middleware::Multipart do
RSpec.shared_examples 'rejecting the invalid key' do |key_in_header:, key_in_upload_params:, error_message:|
let(:rewritten_fields) { rewritten_fields_hash(key_in_header => uploaded_filepath) }
- let(:params) { upload_parameters_for(filepath: uploaded_filepath, key: key_in_upload_params, filename: filename, remote_id: remote_id) }
+ let(:params) { upload_parameters_for(filepath: uploaded_filepath, key: key_in_upload_params, mode: mode, filename: filename, remote_id: remote_id) }
it 'raises an error' do
expect { subject }.to raise_error(RuntimeError, error_message)
@@ -171,7 +167,7 @@ RSpec.describe Gitlab::Middleware::Multipart do
let(:rewritten_fields) { rewritten_fields_hash('file' => uploaded_filepath) }
let(:crafted_payload) { Base64.urlsafe_encode64({ 'path' => 'test' }.to_json) }
let(:params) do
- upload_parameters_for(filepath: uploaded_filepath, key: 'file', filename: filename, remote_id: remote_id).tap do |params|
+ upload_parameters_for(filepath: uploaded_filepath, key: 'file', mode: mode, filename: filename, remote_id: remote_id).tap do |params|
header, _, sig = params['file.gitlab-workhorse-upload'].split('.')
params['file.gitlab-workhorse-upload'] = [header, crafted_payload, sig].join('.')
end
@@ -187,7 +183,7 @@ RSpec.describe Gitlab::Middleware::Multipart do
let(:rewritten_fields) { rewritten_fields_hash('file' => uploaded_filepath) }
let(:params) do
- upload_parameters_for(filepath: uploaded_filepath, key: 'file', filename: filename, remote_id: remote_id).tap do |params|
+ upload_parameters_for(filepath: uploaded_filepath, key: 'file', mode: mode, filename: filename, remote_id: remote_id).tap do |params|
header, payload, sig = params['file.gitlab-workhorse-upload'].split('.')
params['file.gitlab-workhorse-upload'] = [header, payload, "#{sig}modified"].join('.')
end
diff --git a/spec/lib/gitlab/middleware/multipart_with_handler_spec.rb b/spec/lib/gitlab/middleware/multipart_with_handler_spec.rb
deleted file mode 100644
index 8c2af775574..00000000000
--- a/spec/lib/gitlab/middleware/multipart_with_handler_spec.rb
+++ /dev/null
@@ -1,196 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Middleware::Multipart do
- include MultipartHelpers
-
- describe '#call' do
- let(:app) { double(:app) }
- let(:middleware) { described_class.new(app) }
- let(:secret) { Gitlab::Workhorse.secret }
- let(:issuer) { 'gitlab-workhorse' }
-
- subject do
- env = post_env(
- rewritten_fields: rewritten_fields,
- params: params,
- secret: secret,
- issuer: issuer
- )
- middleware.call(env)
- end
-
- before do
- stub_feature_flags(upload_middleware_jwt_params_handler: false)
- end
-
- context 'remote file mode' do
- let(:mode) { :remote }
-
- it_behaves_like 'handling all upload parameters conditions'
-
- context 'and a path set' do
- include_context 'with one temporary file for multipart'
-
- let(:rewritten_fields) { rewritten_fields_hash('file' => uploaded_filepath) }
- let(:params) { upload_parameters_for(key: 'file', filename: filename, remote_id: remote_id).merge('file.path' => '/should/not/be/read') }
-
- it 'builds an UploadedFile' do
- expect_uploaded_files(original_filename: filename, remote_id: remote_id, size: uploaded_file.size, params_path: %w(file))
-
- subject
- end
- end
- end
-
- context 'local file mode' do
- let(:mode) { :local }
-
- it_behaves_like 'handling all upload parameters conditions'
-
- context 'when file is' do
- include_context 'with one temporary file for multipart'
-
- let(:allowed_paths) { [Dir.tmpdir] }
-
- before do
- expect_next_instance_of(::Gitlab::Middleware::Multipart::Handler) do |handler|
- expect(handler).to receive(:allowed_paths).and_return(allowed_paths)
- end
- end
-
- context 'in allowed paths' do
- let(:rewritten_fields) { rewritten_fields_hash('file' => uploaded_filepath) }
- let(:params) { upload_parameters_for(filepath: uploaded_filepath, key: 'file', filename: filename) }
-
- it 'builds an UploadedFile' do
- expect_uploaded_files(filepath: uploaded_filepath, original_filename: filename, size: uploaded_file.size, params_path: %w(file))
-
- subject
- end
- end
-
- context 'not in allowed paths' do
- let(:allowed_paths) { [] }
-
- let(:rewritten_fields) { rewritten_fields_hash('file' => uploaded_filepath) }
- let(:params) { upload_parameters_for(filepath: uploaded_filepath, key: 'file') }
-
- it 'returns an error' do
- result = subject
-
- expect(result[0]).to eq(400)
- expect(result[2]).to include('insecure path used')
- end
- end
- end
- end
-
- context 'with dummy params in remote mode' do
- let(:rewritten_fields) { { 'file' => 'should/not/be/read' } }
- let(:params) { upload_parameters_for(key: 'file') }
- let(:mode) { :remote }
-
- context 'with an invalid secret' do
- let(:secret) { 'INVALID_SECRET' }
-
- it { expect { subject }.to raise_error(JWT::VerificationError) }
- end
-
- context 'with an invalid issuer' do
- let(:issuer) { 'INVALID_ISSUER' }
-
- it { expect { subject }.to raise_error(JWT::InvalidIssuerError) }
- end
-
- context 'with invalid rewritten field key' do
- invalid_keys = [
- '[file]',
- ';file',
- 'file]',
- ';file]',
- 'file]]',
- 'file;;'
- ]
-
- invalid_keys.each do |invalid_key|
- context invalid_key do
- let(:rewritten_fields) { { invalid_key => 'should/not/be/read' } }
-
- it { expect { subject }.to raise_error(RuntimeError, "invalid field: \"#{invalid_key}\"") }
- end
- end
- end
-
- context 'with invalid key in parameters' do
- include_context 'with one temporary file for multipart'
-
- let(:rewritten_fields) { rewritten_fields_hash('file' => uploaded_filepath) }
- let(:params) { upload_parameters_for(filepath: uploaded_filepath, key: 'wrong_key', filename: filename, remote_id: remote_id) }
-
- it 'builds no UploadedFile' do
- expect(app).to receive(:call) do |env|
- received_params = get_params(env)
- expect(received_params['file']).to be_nil
- expect(received_params['wrong_key']).to be_nil
- end
-
- subject
- end
- end
-
- context 'with invalid key in header' do
- include_context 'with one temporary file for multipart'
-
- RSpec.shared_examples 'rejecting the invalid key' do |key_in_header:, key_in_upload_params:, error_message:|
- let(:rewritten_fields) { rewritten_fields_hash(key_in_header => uploaded_filepath) }
- let(:params) { upload_parameters_for(filepath: uploaded_filepath, key: key_in_upload_params, filename: filename, remote_id: remote_id) }
-
- it 'raises an error' do
- expect { subject }.to raise_error(RuntimeError, error_message)
- end
- end
-
- it_behaves_like 'rejecting the invalid key',
- key_in_header: 'user[avatar',
- key_in_upload_params: 'user[avatar]',
- error_message: 'invalid field: "user[avatar"'
- it_behaves_like 'rejecting the invalid key',
- key_in_header: '[user]avatar',
- key_in_upload_params: 'user[avatar]',
- error_message: 'invalid field: "[user]avatar"'
- it_behaves_like 'rejecting the invalid key',
- key_in_header: 'user[]avatar',
- key_in_upload_params: 'user[avatar]',
- error_message: 'invalid field: "user[]avatar"'
- it_behaves_like 'rejecting the invalid key',
- key_in_header: 'user[avatar[image[url]]]',
- key_in_upload_params: 'user[avatar]',
- error_message: 'invalid field: "user[avatar[image[url]]]"'
- it_behaves_like 'rejecting the invalid key',
- key_in_header: '[]',
- key_in_upload_params: 'user[avatar]',
- error_message: 'invalid field: "[]"'
- it_behaves_like 'rejecting the invalid key',
- key_in_header: 'x' * 11000,
- key_in_upload_params: 'user[avatar]',
- error_message: "invalid field: \"#{'x' * 11000}\""
- end
-
- context 'with key with unbalanced brackets in header' do
- include_context 'with one temporary file for multipart'
-
- let(:invalid_key) { 'user[avatar' }
- let(:rewritten_fields) { rewritten_fields_hash( invalid_key => uploaded_filepath) }
- let(:params) { upload_parameters_for(filepath: uploaded_filepath, key: 'user[avatar]', filename: filename, remote_id: remote_id) }
-
- it 'builds no UploadedFile' do
- expect(app).not_to receive(:call)
-
- expect { subject }.to raise_error(RuntimeError, "invalid field: \"#{invalid_key}\"")
- end
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/path_regex_spec.rb b/spec/lib/gitlab/path_regex_spec.rb
index 8e9f7e372c5..cd89674af0f 100644
--- a/spec/lib/gitlab/path_regex_spec.rb
+++ b/spec/lib/gitlab/path_regex_spec.rb
@@ -102,6 +102,7 @@ RSpec.describe Gitlab::PathRegex do
.concat(files_in_public)
.concat(Array(API::API.prefix.to_s))
.concat(sitemap_words)
+ .concat(deprecated_routes)
.compact
.uniq
end
@@ -110,6 +111,11 @@ RSpec.describe Gitlab::PathRegex do
%w(sitemap sitemap.xml sitemap.xml.gz)
end
+ let(:deprecated_routes) do
+ # profile was deprecated in https://gitlab.com/gitlab-org/gitlab/-/merge_requests/51646
+ %w(profile)
+ end
+
let(:ee_top_level_words) do
%w(unsubscribes v2)
end
diff --git a/spec/lib/gitlab/performance_bar/redis_adapter_when_peek_enabled_spec.rb b/spec/lib/gitlab/performance_bar/redis_adapter_when_peek_enabled_spec.rb
index bbc8b0d67e0..05cdc5bb79b 100644
--- a/spec/lib/gitlab/performance_bar/redis_adapter_when_peek_enabled_spec.rb
+++ b/spec/lib/gitlab/performance_bar/redis_adapter_when_peek_enabled_spec.rb
@@ -31,6 +31,7 @@ RSpec.describe Gitlab::PerformanceBar::RedisAdapterWhenPeekEnabled do
expect_to_obtain_exclusive_lease(GitlabPerformanceBarStatsWorker::LEASE_KEY, uuid)
expect(GitlabPerformanceBarStatsWorker).to receive(:perform_in).with(GitlabPerformanceBarStatsWorker::WORKER_DELAY, uuid)
expect(client).to receive(:sadd).with(GitlabPerformanceBarStatsWorker::STATS_KEY, uuid)
+ expect(client).to receive(:expire).with(GitlabPerformanceBarStatsWorker::STATS_KEY, GitlabPerformanceBarStatsWorker::STATS_KEY_EXPIRE)
peek_adapter.new(client).save('foo')
end
diff --git a/spec/lib/gitlab/project_template_spec.rb b/spec/lib/gitlab/project_template_spec.rb
index 98bd2efdbc6..4eb13e63b46 100644
--- a/spec/lib/gitlab/project_template_spec.rb
+++ b/spec/lib/gitlab/project_template_spec.rb
@@ -11,6 +11,7 @@ RSpec.describe Gitlab::ProjectTemplate do
hexo sse_middleman gitpod_spring_petclinic nfhugo
nfjekyll nfplainhtml nfgitbook nfhexo salesforcedx
serverless_framework jsonnet cluster_management
+ kotlin_native_linux
]
expect(described_class.all).to be_an(Array)
diff --git a/spec/lib/gitlab/prometheus/internal_spec.rb b/spec/lib/gitlab/prometheus/internal_spec.rb
index 7771d85222a..b08b8813470 100644
--- a/spec/lib/gitlab/prometheus/internal_spec.rb
+++ b/spec/lib/gitlab/prometheus/internal_spec.rb
@@ -3,12 +3,12 @@
require 'spec_helper'
RSpec.describe Gitlab::Prometheus::Internal do
- let(:listen_address) { 'localhost:9090' }
+ let(:server_address) { 'localhost:9090' }
let(:prometheus_settings) do
{
- enable: true,
- listen_address: listen_address
+ enabled: true,
+ server_address: server_address
}
end
@@ -27,25 +27,25 @@ RSpec.describe Gitlab::Prometheus::Internal do
it_behaves_like 'returns valid uri', 'http://localhost:9090'
context 'with non default prometheus address' do
- let(:listen_address) { 'https://localhost:9090' }
+ let(:server_address) { 'https://localhost:9090' }
it_behaves_like 'returns valid uri', 'https://localhost:9090'
context 'with :9090 symbol' do
- let(:listen_address) { :':9090' }
+ let(:server_address) { :':9090' }
it_behaves_like 'returns valid uri', 'http://localhost:9090'
end
context 'with 0.0.0.0:9090' do
- let(:listen_address) { '0.0.0.0:9090' }
+ let(:server_address) { '0.0.0.0:9090' }
it_behaves_like 'returns valid uri', 'http://localhost:9090'
end
end
- context 'when listen_address is nil' do
- let(:listen_address) { nil }
+ context 'when server_address is nil' do
+ let(:server_address) { nil }
it 'does not fail' do
expect(described_class.uri).to be_nil
@@ -53,7 +53,7 @@ RSpec.describe Gitlab::Prometheus::Internal do
end
context 'when prometheus listen address is blank in gitlab.yml' do
- let(:listen_address) { '' }
+ let(:server_address) { '' }
it 'does not configure prometheus' do
expect(described_class.uri).to be_nil
@@ -61,26 +61,6 @@ RSpec.describe Gitlab::Prometheus::Internal do
end
end
- describe '.server_address' do
- context 'self.uri returns valid uri' do
- ['http://localhost:9090', 'https://localhost:9090 '].each do |valid_uri|
- it 'returns correct server address' do
- expect(described_class).to receive(:uri).and_return(valid_uri)
-
- expect(described_class.server_address).to eq('localhost:9090')
- end
- end
- end
-
- context 'self.uri returns nil' do
- it 'returns nil' do
- expect(described_class).to receive(:uri).and_return(nil)
-
- expect(described_class.server_address).to be_nil
- end
- end
- end
-
describe '.prometheus_enabled?' do
it 'returns correct value' do
expect(described_class.prometheus_enabled?).to eq(true)
@@ -89,8 +69,8 @@ RSpec.describe Gitlab::Prometheus::Internal do
context 'when prometheus setting is disabled in gitlab.yml' do
let(:prometheus_settings) do
{
- enable: false,
- listen_address: listen_address
+ enabled: false,
+ server_address: server_address
}
end
@@ -110,9 +90,9 @@ RSpec.describe Gitlab::Prometheus::Internal do
end
end
- describe '.listen_address' do
+ describe '.server_address' do
it 'returns correct value' do
- expect(described_class.listen_address).to eq(listen_address)
+ expect(described_class.server_address).to eq(server_address)
end
context 'when prometheus setting is not present in gitlab.yml' do
@@ -121,7 +101,7 @@ RSpec.describe Gitlab::Prometheus::Internal do
end
it 'does not fail' do
- expect(described_class.listen_address).to be_nil
+ expect(described_class.server_address).to be_nil
end
end
end
diff --git a/spec/lib/gitlab/rack_attack_spec.rb b/spec/lib/gitlab/rack_attack_spec.rb
index d72863b0103..5748e1e49e5 100644
--- a/spec/lib/gitlab/rack_attack_spec.rb
+++ b/spec/lib/gitlab/rack_attack_spec.rb
@@ -22,8 +22,7 @@ RSpec.describe Gitlab::RackAttack, :aggregate_failures do
stub_const("Rack::Attack", fake_rack_attack)
stub_const("Rack::Attack::Request", fake_rack_attack_request)
- # Expect rather than just allow, because this is actually fairly important functionality
- expect(fake_rack_attack).to receive(:throttled_response_retry_after_header=).with(true)
+ allow(fake_rack_attack).to receive(:throttled_response=)
allow(fake_rack_attack).to receive(:throttle)
allow(fake_rack_attack).to receive(:track)
allow(fake_rack_attack).to receive(:safelist)
@@ -36,6 +35,12 @@ RSpec.describe Gitlab::RackAttack, :aggregate_failures do
expect(fake_rack_attack_request).to include(described_class::Request)
end
+ it 'configures the throttle response' do
+ described_class.configure(fake_rack_attack)
+
+ expect(fake_rack_attack).to have_received(:throttled_response=).with(an_instance_of(Proc))
+ end
+
it 'configures the safelist' do
described_class.configure(fake_rack_attack)
@@ -93,4 +98,207 @@ RSpec.describe Gitlab::RackAttack, :aggregate_failures do
end
end
end
+
+ describe '.throttled_response_headers' do
+ where(:matched, :match_data, :headers) do
+ [
+ [
+ 'throttle_unauthenticated',
+ {
+ discriminator: '127.0.0.1',
+ count: 3700,
+ period: 1.hour,
+ limit: 3600,
+ epoch_time: Time.utc(2021, 1, 5, 10, 29, 30).to_i
+ },
+ {
+ 'RateLimit-Name' => 'throttle_unauthenticated',
+ 'RateLimit-Limit' => '60',
+ 'RateLimit-Observed' => '3700',
+ 'RateLimit-Remaining' => '0',
+ 'RateLimit-Reset' => '1609844400', # Time.utc(2021, 1, 5, 11, 0, 0).to_i.to_s
+ 'RateLimit-ResetTime' => 'Tue, 05 Jan 2021 11:00:00 GMT',
+ 'Retry-After' => '1830'
+ }
+ ],
+ [
+ 'throttle_unauthenticated',
+ {
+ discriminator: '127.0.0.1',
+ count: 3700,
+ period: 1.hour,
+ limit: 3600,
+ epoch_time: Time.utc(2021, 1, 5, 10, 59, 59).to_i
+ },
+ {
+ 'RateLimit-Name' => 'throttle_unauthenticated',
+ 'RateLimit-Limit' => '60',
+ 'RateLimit-Observed' => '3700',
+ 'RateLimit-Remaining' => '0',
+ 'RateLimit-Reset' => '1609844400', # Time.utc(2021, 1, 5, 11, 0, 0).to_i.to_s
+ 'RateLimit-ResetTime' => 'Tue, 05 Jan 2021 11:00:00 GMT',
+ 'Retry-After' => '1'
+ }
+ ],
+ [
+ 'throttle_unauthenticated',
+ {
+ discriminator: '127.0.0.1',
+ count: 3700,
+ period: 1.hour,
+ limit: 3600,
+ epoch_time: Time.utc(2021, 1, 5, 10, 0, 0).to_i
+ },
+ {
+ 'RateLimit-Name' => 'throttle_unauthenticated',
+ 'RateLimit-Limit' => '60',
+ 'RateLimit-Observed' => '3700',
+ 'RateLimit-Remaining' => '0',
+ 'RateLimit-Reset' => '1609844400', # Time.utc(2021, 1, 5, 11, 0, 0).to_i.to_s
+ 'RateLimit-ResetTime' => 'Tue, 05 Jan 2021 11:00:00 GMT',
+ 'Retry-After' => '3600'
+ }
+ ],
+ [
+ 'throttle_unauthenticated',
+ {
+ discriminator: '127.0.0.1',
+ count: 3700,
+ period: 1.hour,
+ limit: 3600,
+ epoch_time: Time.utc(2021, 1, 5, 23, 30, 0).to_i
+ },
+ {
+ 'RateLimit-Name' => 'throttle_unauthenticated',
+ 'RateLimit-Limit' => '60',
+ 'RateLimit-Observed' => '3700',
+ 'RateLimit-Remaining' => '0',
+ 'RateLimit-Reset' => '1609891200', # Time.utc(2021, 1, 6, 0, 0, 0).to_i.to_s
+ 'RateLimit-ResetTime' => 'Wed, 06 Jan 2021 00:00:00 GMT', # Next day
+ 'Retry-After' => '1800'
+ }
+ ],
+ [
+ 'throttle_unauthenticated',
+ {
+ discriminator: '127.0.0.1',
+ count: 3700,
+ period: 1.hour,
+ limit: 3400,
+ epoch_time: Time.utc(2021, 1, 5, 10, 30, 0).to_i
+ },
+ {
+ 'RateLimit-Name' => 'throttle_unauthenticated',
+ 'RateLimit-Limit' => '57', # 56.66 requests per minute
+ 'RateLimit-Observed' => '3700',
+ 'RateLimit-Remaining' => '0',
+ 'RateLimit-Reset' => '1609844400', # Time.utc(2021, 1, 5, 11, 0, 0).to_i.to_s
+ 'RateLimit-ResetTime' => 'Tue, 05 Jan 2021 11:00:00 GMT',
+ 'Retry-After' => '1800'
+ }
+ ],
+ [
+ 'throttle_unauthenticated',
+ {
+ discriminator: '127.0.0.1',
+ count: 3700,
+ period: 1.hour,
+ limit: 3700,
+ epoch_time: Time.utc(2021, 1, 5, 10, 30, 0).to_i
+ },
+ {
+ 'RateLimit-Name' => 'throttle_unauthenticated',
+ 'RateLimit-Limit' => '62', # 61.66 requests per minute
+ 'RateLimit-Observed' => '3700',
+ 'RateLimit-Remaining' => '0',
+ 'RateLimit-Reset' => '1609844400', # Time.utc(2021, 1, 5, 11, 0, 0).to_i.to_s
+ 'RateLimit-ResetTime' => 'Tue, 05 Jan 2021 11:00:00 GMT',
+ 'Retry-After' => '1800'
+ }
+ ],
+ [
+ 'throttle_unauthenticated',
+ {
+ discriminator: '127.0.0.1',
+ count: 3700,
+ period: 1.hour,
+ limit: 59,
+ epoch_time: Time.utc(2021, 1, 5, 10, 30, 0).to_i
+ },
+ {
+ 'RateLimit-Name' => 'throttle_unauthenticated',
+ 'RateLimit-Limit' => '1', # 0.9833 requests per minute
+ 'RateLimit-Observed' => '3700',
+ 'RateLimit-Remaining' => '0',
+ 'RateLimit-Reset' => '1609844400', # Time.utc(2021, 1, 5, 11, 0, 0).to_i.to_s
+ 'RateLimit-ResetTime' => 'Tue, 05 Jan 2021 11:00:00 GMT',
+ 'Retry-After' => '1800'
+ }
+ ],
+ [
+ 'throttle_unauthenticated',
+ {
+ discriminator: '127.0.0.1',
+ count: 3700,
+ period: 1.hour,
+ limit: 61,
+ epoch_time: Time.utc(2021, 1, 5, 10, 30, 0).to_i
+ },
+ {
+ 'RateLimit-Name' => 'throttle_unauthenticated',
+ 'RateLimit-Limit' => '2', # 1.016 requests per minute
+ 'RateLimit-Observed' => '3700',
+ 'RateLimit-Remaining' => '0',
+ 'RateLimit-Reset' => '1609844400', # Time.utc(2021, 1, 5, 11, 0, 0).to_i.to_s
+ 'RateLimit-ResetTime' => 'Tue, 05 Jan 2021 11:00:00 GMT',
+ 'Retry-After' => '1800'
+ }
+ ],
+ [
+ 'throttle_unauthenticated',
+ {
+ discriminator: '127.0.0.1',
+ count: 3700,
+ period: 15.seconds,
+ limit: 10,
+ epoch_time: Time.utc(2021, 1, 5, 10, 30, 0).to_i
+ },
+ {
+ 'RateLimit-Name' => 'throttle_unauthenticated',
+ 'RateLimit-Limit' => '40',
+ 'RateLimit-Observed' => '3700',
+ 'RateLimit-Remaining' => '0',
+ 'RateLimit-Reset' => '1609842615', # Time.utc(2021, 1, 5, 10, 30, 15).to_i.to_s
+ 'RateLimit-ResetTime' => 'Tue, 05 Jan 2021 10:30:15 GMT',
+ 'Retry-After' => '15'
+ }
+ ],
+ [
+ 'throttle_unauthenticated',
+ {
+ discriminator: '127.0.0.1',
+ count: 3700,
+ period: 27.seconds,
+ limit: 10,
+ epoch_time: Time.utc(2021, 1, 5, 10, 30, 0).to_i
+ },
+ {
+ 'RateLimit-Name' => 'throttle_unauthenticated',
+ 'RateLimit-Limit' => '23',
+ 'RateLimit-Observed' => '3700',
+ 'RateLimit-Remaining' => '0',
+ 'RateLimit-Reset' => '1609842627', # Time.utc(2021, 1, 5, 10, 30, 27).to_i.to_s
+ 'RateLimit-ResetTime' => 'Tue, 05 Jan 2021 10:30:27 GMT',
+ 'Retry-After' => '27'
+ }
+ ]
+ ]
+ end
+
+ with_them do
+ it 'generates accurate throttled headers' do
+ expect(described_class.throttled_response_headers(matched, match_data)).to eql(headers)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb
index 57be9e93af2..c437b6bcceb 100644
--- a/spec/lib/gitlab/search_results_spec.rb
+++ b/spec/lib/gitlab/search_results_spec.rb
@@ -342,17 +342,36 @@ RSpec.describe Gitlab::SearchResults do
expect(results.limited_issues_count).to eq 4
end
- it 'lists all issues for admin' do
- results = described_class.new(admin, query, limit_projects)
- issues = results.objects('issues')
+ context 'with admin user' do
+ context 'when admin mode enabled', :enable_admin_mode do
+ it 'lists all issues' do
+ results = described_class.new(admin, query, limit_projects)
+ issues = results.objects('issues')
+
+ expect(issues).to include issue
+ expect(issues).to include security_issue_1
+ expect(issues).to include security_issue_2
+ expect(issues).to include security_issue_3
+ expect(issues).to include security_issue_4
+ expect(issues).not_to include security_issue_5
+ expect(results.limited_issues_count).to eq 5
+ end
+ end
- expect(issues).to include issue
- expect(issues).to include security_issue_1
- expect(issues).to include security_issue_2
- expect(issues).to include security_issue_3
- expect(issues).to include security_issue_4
- expect(issues).not_to include security_issue_5
- expect(results.limited_issues_count).to eq 5
+ context 'when admin mode disabled' do
+ it 'does not list confidential issues' do
+ results = described_class.new(admin, query, limit_projects)
+ issues = results.objects('issues')
+
+ expect(issues).to include issue
+ expect(issues).not_to include security_issue_1
+ expect(issues).not_to include security_issue_2
+ expect(issues).not_to include security_issue_3
+ expect(issues).not_to include security_issue_4
+ expect(issues).not_to include security_issue_5
+ expect(results.limited_issues_count).to eq 1
+ end
+ end
end
end
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 3d9ffb11ae2..3ba08455d01 100644
--- a/spec/lib/gitlab/sidekiq_middleware/admin_mode/client_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/admin_mode/client_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::SidekiqMiddleware::AdminMode::Client, :do_not_mock_admin_mode, :request_store do
+RSpec.describe Gitlab::SidekiqMiddleware::AdminMode::Client, :request_store do
include AdminModeHelper
let(:worker) 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 20f1e88bcf4..e8322b11875 100644
--- a/spec/lib/gitlab/sidekiq_middleware/admin_mode/server_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/admin_mode/server_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::SidekiqMiddleware::AdminMode::Server, :do_not_mock_admin_mode, :request_store do
+RSpec.describe Gitlab::SidekiqMiddleware::AdminMode::Server, :request_store do
include AdminModeHelper
let(:worker) do
diff --git a/spec/lib/gitlab/sidekiq_middleware_spec.rb b/spec/lib/gitlab/sidekiq_middleware_spec.rb
index 4ee9569a0cf..b632fc8bad2 100644
--- a/spec/lib/gitlab/sidekiq_middleware_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware_spec.rb
@@ -100,7 +100,7 @@ RSpec.describe Gitlab::SidekiqMiddleware do
"subject",
"body"
],
- "_aj_symbol_keys" => ["args"]
+ ActiveJob::Arguments.const_get('RUBY2_KEYWORDS_KEY', false) => ["args"]
}
],
"executions" => 0,
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 df949154d4c..a4d8e3957cf 100644
--- a/spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb
@@ -3,15 +3,20 @@
require 'spec_helper'
RSpec.describe Gitlab::SlashCommands::Presenters::IssueMove do
- let_it_be(:admin) { create(:admin) }
+ let_it_be(:user) { create(:user) }
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, admin).execute(old_issue, other_project) }
+ let(:new_issue) { Issues::MoveService.new(project, user).execute(old_issue, other_project) }
let(:attachment) { subject[:attachments].first }
subject { described_class.new(new_issue).present(old_issue) }
+ before do
+ project.add_developer(user)
+ other_project.add_developer(user)
+ end
+
it { is_expected.to be_a(Hash) }
it 'shows the new issue' do
diff --git a/spec/lib/gitlab/template/gitlab_ci_syntax_yml_template_spec.rb b/spec/lib/gitlab/template/gitlab_ci_syntax_yml_template_spec.rb
new file mode 100644
index 00000000000..d1024019a9f
--- /dev/null
+++ b/spec/lib/gitlab/template/gitlab_ci_syntax_yml_template_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Template::GitlabCiSyntaxYmlTemplate do
+ subject { described_class }
+
+ describe '#content' do
+ it 'loads the full file' do
+ template = subject.new(Rails.root.join('lib/gitlab/ci/syntax_templates/Artifacts example.gitlab-ci.yml'))
+
+ expect(template.content).to start_with('#')
+ end
+ end
+
+ it_behaves_like 'file template shared examples', 'Artifacts example', '.gitlab-ci.yml'
+end
diff --git a/spec/lib/gitlab/throttle_spec.rb b/spec/lib/gitlab/throttle_spec.rb
index 7462b2e1c38..50d723193ac 100644
--- a/spec/lib/gitlab/throttle_spec.rb
+++ b/spec/lib/gitlab/throttle_spec.rb
@@ -30,4 +30,32 @@ RSpec.describe Gitlab::Throttle do
end
end
end
+
+ describe '.rate_limiting_response_text' do
+ subject { described_class.rate_limiting_response_text }
+
+ context 'when the setting is not present' do
+ before do
+ stub_application_setting(rate_limiting_response_text: '')
+ end
+
+ it 'returns the default value with a trailing newline' do
+ expect(subject).to eq(described_class::DEFAULT_RATE_LIMITING_RESPONSE_TEXT + "\n")
+ end
+ end
+
+ context 'when the setting is present' do
+ let(:response_text) do
+ 'Rate limit exceeded; see https://docs.gitlab.com/ee/user/gitlab_com/#gitlabcom-specific-rate-limits for more details'
+ end
+
+ before do
+ stub_application_setting(rate_limiting_response_text: response_text)
+ end
+
+ it 'returns the default value with a trailing newline' do
+ expect(subject).to eq(response_text + "\n")
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/tracking/standard_context_spec.rb b/spec/lib/gitlab/tracking/standard_context_spec.rb
new file mode 100644
index 00000000000..acf7aeb303a
--- /dev/null
+++ b/spec/lib/gitlab/tracking/standard_context_spec.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Tracking::StandardContext do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:namespace) { create(:namespace) }
+
+ let(:snowplow_context) { subject.to_context }
+
+ describe '#to_context' do
+ context 'with no arguments' do
+ it 'creates a Snowplow context with no data' do
+ snowplow_context.to_json[:data].each do |_, v|
+ expect(v).to be_nil
+ end
+ end
+ end
+
+ context 'with extra data' do
+ subject { described_class.new(foo: 'bar') }
+
+ it 'creates a Snowplow context with the given data' do
+ expect(snowplow_context.to_json.dig(:data, :foo)).to eq('bar')
+ end
+ end
+
+ context 'with namespace' do
+ subject { described_class.new(namespace: namespace) }
+
+ it 'creates a Snowplow context using the given data' do
+ expect(snowplow_context.to_json.dig(:data, :namespace_id)).to eq(namespace.id)
+ expect(snowplow_context.to_json.dig(:data, :project_id)).to be_nil
+ end
+ end
+
+ context 'with project' do
+ subject { described_class.new(project: project) }
+
+ it 'creates a Snowplow context using the given data' do
+ expect(snowplow_context.to_json.dig(:data, :namespace_id)).to eq(project.namespace.id)
+ expect(snowplow_context.to_json.dig(:data, :project_id)).to eq(project.id)
+ end
+ end
+
+ context 'with project and namespace' do
+ subject { described_class.new(namespace: namespace, project: project) }
+
+ it 'creates a Snowplow context using the given data' do
+ expect(snowplow_context.to_json.dig(:data, :namespace_id)).to eq(namespace.id)
+ expect(snowplow_context.to_json.dig(:data, :project_id)).to eq(project.id)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/tracking_spec.rb b/spec/lib/gitlab/tracking_spec.rb
index 57882de0974..8f1fd49f4c5 100644
--- a/spec/lib/gitlab/tracking_spec.rb
+++ b/spec/lib/gitlab/tracking_spec.rb
@@ -41,21 +41,42 @@ RSpec.describe Gitlab::Tracking do
allow_any_instance_of(Gitlab::Tracking::Destinations::ProductAnalytics).to receive(:event)
end
- it 'delegates to snowplow destination' do
- expect_any_instance_of(Gitlab::Tracking::Destinations::Snowplow)
- .to receive(:event)
- .with('category', 'action', label: 'label', property: 'property', value: 1.5, context: nil)
+ shared_examples 'delegates to destination' do |klass|
+ context 'with standard context' do
+ it "delegates to #{klass} destination" do
+ expect_any_instance_of(klass).to receive(:event) do |_, category, action, args|
+ expect(category).to eq('category')
+ expect(action).to eq('action')
+ expect(args[:label]).to eq('label')
+ expect(args[:property]).to eq('property')
+ expect(args[:value]).to eq(1.5)
+ expect(args[:context].length).to eq(1)
+ expect(args[:context].first.to_json[:schema]).to eq(Gitlab::Tracking::StandardContext::GITLAB_STANDARD_SCHEMA_URL)
+ expect(args[:context].first.to_json[:data]).to include(foo: 'bar')
+ end
- described_class.event('category', 'action', label: 'label', property: 'property', value: 1.5)
- end
+ described_class.event('category', 'action', label: 'label', property: 'property', value: 1.5,
+ standard_context: Gitlab::Tracking::StandardContext.new(foo: 'bar'))
+ end
+ end
- it 'delegates to ProductAnalytics destination' do
- expect_any_instance_of(Gitlab::Tracking::Destinations::ProductAnalytics)
- .to receive(:event)
- .with('category', 'action', label: 'label', property: 'property', value: 1.5, context: nil)
+ context 'without standard context' do
+ it "delegates to #{klass} destination" do
+ expect_any_instance_of(klass).to receive(:event) do |_, category, action, args|
+ expect(category).to eq('category')
+ expect(action).to eq('action')
+ expect(args[:label]).to eq('label')
+ expect(args[:property]).to eq('property')
+ expect(args[:value]).to eq(1.5)
+ end
- described_class.event('category', 'action', label: 'label', property: 'property', value: 1.5)
+ described_class.event('category', 'action', label: 'label', property: 'property', value: 1.5)
+ end
+ end
end
+
+ include_examples 'delegates to destination', Gitlab::Tracking::Destinations::Snowplow
+ include_examples 'delegates to destination', Gitlab::Tracking::Destinations::ProductAnalytics
end
describe '.self_describing_event' do
diff --git a/spec/lib/gitlab/url_builder_spec.rb b/spec/lib/gitlab/url_builder_spec.rb
index c892f1f0410..6d055fe3643 100644
--- a/spec/lib/gitlab/url_builder_spec.rb
+++ b/spec/lib/gitlab/url_builder_spec.rb
@@ -18,6 +18,8 @@ RSpec.describe Gitlab::UrlBuilder do
where(:factory, :path_generator) do
:project | ->(project) { "/#{project.full_path}" }
+ :board | ->(board) { "/#{board.project.full_path}/-/boards/#{board.id}" }
+ :group_board | ->(board) { "/groups/#{board.group.full_path}/-/boards/#{board.id}" }
:commit | ->(commit) { "/#{commit.project.full_path}/-/commit/#{commit.id}" }
:issue | ->(issue) { "/#{issue.project.full_path}/-/issues/#{issue.iid}" }
:merge_request | ->(merge_request) { "/#{merge_request.project.full_path}/-/merge_requests/#{merge_request.iid}" }
diff --git a/spec/lib/gitlab/usage/metric_definition_spec.rb b/spec/lib/gitlab/usage/metric_definition_spec.rb
new file mode 100644
index 00000000000..e101f837324
--- /dev/null
+++ b/spec/lib/gitlab/usage/metric_definition_spec.rb
@@ -0,0 +1,123 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Usage::MetricDefinition do
+ let(:attributes) do
+ {
+ name: 'uuid',
+ description: 'GitLab instance unique identifier',
+ value_type: 'string',
+ product_category: 'collection',
+ stage: 'growth',
+ status: 'data_available',
+ default_generation: 'generation_1',
+ full_path: {
+ generation_1: 'uuid',
+ generation_2: 'license.uuid'
+ },
+ 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(:path) { File.join('metrics', 'uuid.yml') }
+ let(:definition) { described_class.new(path, attributes) }
+ let(:yaml_content) { attributes.deep_stringify_keys.to_yaml }
+
+ it 'has all definitons valid' do
+ expect { described_class.definitions }.not_to raise_error(Gitlab::Usage::Metric::InvalidMetricError)
+ end
+
+ describe '#key' do
+ subject { definition.key }
+
+ it 'returns a symbol from name' do
+ is_expected.to eq('uuid')
+ end
+ end
+
+ describe '#validate' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:attribute, :value) do
+ :name | nil
+ :description | nil
+ :value_type | nil
+ :value_type | 'test'
+ :status | nil
+ :default_generation | nil
+ :group | nil
+ :time_frame | nil
+ :time_frame | '29d'
+ :data_source | 'other'
+ :data_source | nil
+ :distribution | nil
+ :distribution | 'test'
+ :tier | %w(test ee)
+ end
+
+ with_them do
+ before do
+ attributes[attribute] = value
+ end
+
+ it 'raise exception' do
+ expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).at_least(:once).with(instance_of(Gitlab::Usage::Metric::InvalidMetricError))
+
+ described_class.new(path, attributes).validate!
+ end
+ end
+ end
+
+ describe '.load_all!' do
+ let(:metric1) { Dir.mktmpdir('metric1') }
+ let(:metric2) { Dir.mktmpdir('metric2') }
+ let(:definitions) { {} }
+
+ before do
+ allow(described_class).to receive(:paths).and_return(
+ [
+ File.join(metric1, '**', '*.yml'),
+ File.join(metric2, '**', '*.yml')
+ ]
+ )
+ end
+
+ subject { described_class.send(:load_all!) }
+
+ it 'has empty list when there are no definition files' do
+ is_expected.to be_empty
+ end
+
+ it 'has one metric when there is one file' do
+ write_metric(metric1, path, yaml_content)
+
+ is_expected.to be_one
+ end
+
+ it 'when the same meric is defined multiple times raises exception' do
+ write_metric(metric1, path, yaml_content)
+ write_metric(metric2, path, yaml_content)
+
+ expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).with(instance_of(Gitlab::Usage::Metric::InvalidMetricError))
+
+ subject
+ end
+
+ after do
+ FileUtils.rm_rf(metric1)
+ FileUtils.rm_rf(metric2)
+ end
+
+ def write_metric(metric, path, content)
+ path = File.join(metric, path)
+ dir = File.dirname(path)
+ FileUtils.mkdir_p(dir)
+ File.write(path, content)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/usage/metric_spec.rb b/spec/lib/gitlab/usage/metric_spec.rb
new file mode 100644
index 00000000000..40671d980d6
--- /dev/null
+++ b/spec/lib/gitlab/usage/metric_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Usage::Metric do
+ describe '#definition' do
+ it 'returns generation_1 metric definiton' do
+ expect(described_class.new(default_generation_path: 'uuid').definition).to be_an(Gitlab::Usage::MetricDefinition)
+ end
+ end
+
+ describe '#unflatten_default_path' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:default_generation_path, :value, :expected_hash) do
+ 'uuid' | nil | { uuid: nil }
+ 'uuid' | '1111' | { uuid: '1111' }
+ 'counts.issues' | nil | { counts: { issues: nil } }
+ 'counts.issues' | 100 | { counts: { issues: 100 } }
+ 'usage_activity_by_stage.verify.ci_builds' | 100 | { usage_activity_by_stage: { verify: { ci_builds: 100 } } }
+ end
+
+ with_them do
+ subject { described_class.new(default_generation_path: default_generation_path, value: value).unflatten_default_path }
+
+ it { is_expected.to eq(expected_hash) }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/usage_data_counters/ci_template_unique_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/ci_template_unique_counter_spec.rb
new file mode 100644
index 00000000000..ba7bfe47bc9
--- /dev/null
+++ b/spec/lib/gitlab/usage_data_counters/ci_template_unique_counter_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::UsageDataCounters::CiTemplateUniqueCounter do
+ let(:project_id) { 1 }
+
+ describe '.track_unique_project_event' do
+ described_class::TEMPLATE_TO_EVENT.keys.each do |template|
+ context "when given template #{template}" do
+ it_behaves_like 'tracking unique hll events', :usage_data_track_ci_templates_unique_projects do
+ subject(:request) { described_class.track_unique_project_event(project_id: project_id, template: template) }
+
+ let(:target_id) { "p_ci_templates_#{described_class::TEMPLATE_TO_EVENT[template]}" }
+ let(:expected_type) { instance_of(Integer) }
+ end
+ end
+ end
+
+ it 'does not track templates outside of TEMPLATE_TO_EVENT' do
+ expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to(
+ receive(:track_event)
+ )
+ Dir.glob(File.join('lib', 'gitlab', 'ci', 'templates', '**'), base: Rails.root) do |template|
+ next if described_class::TEMPLATE_TO_EVENT.key?(template)
+
+ described_class.track_unique_project_event(project_id: 1, template: template)
+ end
+ end
+ end
+end
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 b6a60c09d3d..b8eddc0ca7f 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
@@ -24,6 +24,8 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
describe '.categories' do
it 'gets all unique category names' do
expect(described_class.categories).to contain_exactly(
+ 'deploy_token_packages',
+ 'user_packages',
'compliance',
'analytics',
'ide_edit',
@@ -34,18 +36,10 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
'testing',
'issues_edit',
'ci_secrets_management',
- 'maven_packages',
- 'npm_packages',
- 'conan_packages',
- 'nuget_packages',
- 'pypi_packages',
- 'composer_packages',
- 'generic_packages',
- 'golang_packages',
- 'debian_packages',
- 'container_packages',
- 'tag_packages',
- 'snippets'
+ 'snippets',
+ 'code_review',
+ 'terraform',
+ 'ci_templates'
)
end
end
@@ -95,7 +89,7 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
it 'does not track the event' do
stub_application_setting(usage_ping_enabled: false)
- described_class.track_event(entity1, weekly_event, Date.current)
+ described_class.track_event(weekly_event, values: entity1, time: Date.current)
expect(Gitlab::Redis::HLL).not_to receive(:add)
end
@@ -109,20 +103,27 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
it 'tracks event when using symbol' do
expect(Gitlab::Redis::HLL).to receive(:add)
- described_class.track_event(entity1, :g_analytics_contribution)
+ described_class.track_event(:g_analytics_contribution, values: entity1)
+ end
+
+ it 'tracks events with multiple values' do
+ values = [entity1, entity2]
+ expect(Gitlab::Redis::HLL).to receive(:add).with(key: /g_{analytics}_contribution/, value: values, expiry: 84.days)
+
+ described_class.track_event(:g_analytics_contribution, values: values)
end
it "raise error if metrics don't have same aggregation" do
- expect { described_class.track_event(entity1, different_aggregation, Date.current) }.to raise_error(Gitlab::UsageDataCounters::HLLRedisCounter::UnknownAggregation)
+ expect { described_class.track_event(different_aggregation, values: entity1, time: Date.current) }.to raise_error(Gitlab::UsageDataCounters::HLLRedisCounter::UnknownAggregation)
end
it 'raise error if metrics of unknown aggregation' do
- expect { described_class.track_event(entity1, 'unknown', Date.current) }.to raise_error(Gitlab::UsageDataCounters::HLLRedisCounter::UnknownEvent)
+ expect { described_class.track_event('unknown', values: entity1, time: Date.current) }.to raise_error(Gitlab::UsageDataCounters::HLLRedisCounter::UnknownEvent)
end
context 'for weekly events' do
it 'sets the keys in Redis to expire automatically after the given expiry time' do
- described_class.track_event(entity1, "g_analytics_contribution")
+ described_class.track_event("g_analytics_contribution", values: entity1)
Gitlab::Redis::SharedState.with do |redis|
keys = redis.scan_each(match: "g_{analytics}_contribution-*").to_a
@@ -135,7 +136,7 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
end
it 'sets the keys in Redis to expire automatically after 6 weeks by default' do
- described_class.track_event(entity1, "g_compliance_dashboard")
+ described_class.track_event("g_compliance_dashboard", values: entity1)
Gitlab::Redis::SharedState.with do |redis|
keys = redis.scan_each(match: "g_{compliance}_dashboard-*").to_a
@@ -150,7 +151,7 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
context 'for daily events' do
it 'sets the keys in Redis to expire after the given expiry time' do
- described_class.track_event(entity1, "g_analytics_search")
+ described_class.track_event("g_analytics_search", values: entity1)
Gitlab::Redis::SharedState.with do |redis|
keys = redis.scan_each(match: "*-g_{analytics}_search").to_a
@@ -163,7 +164,7 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
end
it 'sets the keys in Redis to expire after 29 days by default' do
- described_class.track_event(entity1, "no_slot")
+ described_class.track_event("no_slot", values: entity1)
Gitlab::Redis::SharedState.with do |redis|
keys = redis.scan_each(match: "*-{no_slot}").to_a
@@ -180,12 +181,19 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
describe '.track_event_in_context' do
context 'with valid contex' do
- it 'increments conext event counte' do
+ it 'increments context event counter' do
expect(Gitlab::Redis::HLL).to receive(:add) do |kwargs|
expect(kwargs[:key]).to match(/^#{default_context}\_.*/)
end
- described_class.track_event_in_context(entity1, context_event, default_context)
+ described_class.track_event_in_context(context_event, values: entity1, context: default_context)
+ end
+
+ it 'tracks events with multiple values' do
+ values = [entity1, entity2]
+ expect(Gitlab::Redis::HLL).to receive(:add).with(key: /g_{analytics}_contribution/, value: values, expiry: 84.days)
+
+ described_class.track_event_in_context(:g_analytics_contribution, values: values, context: default_context)
end
end
@@ -193,7 +201,7 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
it 'does not increment a counter' do
expect(Gitlab::Redis::HLL).not_to receive(:add)
- described_class.track_event_in_context(entity1, context_event, '')
+ described_class.track_event_in_context(context_event, values: entity1, context: '')
end
end
@@ -201,7 +209,7 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
it 'does not increment a counter' do
expect(Gitlab::Redis::HLL).not_to receive(:add)
- described_class.track_event_in_context(entity1, context_event, invalid_context)
+ described_class.track_event_in_context(context_event, values: entity1, context: invalid_context)
end
end
end
@@ -209,35 +217,35 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
describe '.unique_events' do
before do
# events in current week, should not be counted as week is not complete
- described_class.track_event(entity1, weekly_event, Date.current)
- described_class.track_event(entity2, weekly_event, Date.current)
+ described_class.track_event(weekly_event, values: entity1, time: Date.current)
+ described_class.track_event(weekly_event, values: entity2, time: Date.current)
# Events last week
- described_class.track_event(entity1, weekly_event, 2.days.ago)
- described_class.track_event(entity1, weekly_event, 2.days.ago)
- described_class.track_event(entity1, no_slot, 2.days.ago)
+ described_class.track_event(weekly_event, values: entity1, time: 2.days.ago)
+ described_class.track_event(weekly_event, values: entity1, time: 2.days.ago)
+ described_class.track_event(no_slot, values: entity1, time: 2.days.ago)
# Events 2 weeks ago
- described_class.track_event(entity1, weekly_event, 2.weeks.ago)
+ described_class.track_event(weekly_event, values: entity1, time: 2.weeks.ago)
# Events 4 weeks ago
- described_class.track_event(entity3, weekly_event, 4.weeks.ago)
- described_class.track_event(entity4, weekly_event, 29.days.ago)
+ described_class.track_event(weekly_event, values: entity3, time: 4.weeks.ago)
+ described_class.track_event(weekly_event, values: entity4, time: 29.days.ago)
# events in current day should be counted in daily aggregation
- described_class.track_event(entity1, daily_event, Date.current)
- described_class.track_event(entity2, daily_event, Date.current)
+ described_class.track_event(daily_event, values: entity1, time: Date.current)
+ described_class.track_event(daily_event, values: entity2, time: Date.current)
# Events last week
- described_class.track_event(entity1, daily_event, 2.days.ago)
- described_class.track_event(entity1, daily_event, 2.days.ago)
+ described_class.track_event(daily_event, values: entity1, time: 2.days.ago)
+ described_class.track_event(daily_event, values: entity1, time: 2.days.ago)
# Events 2 weeks ago
- described_class.track_event(entity1, daily_event, 14.days.ago)
+ described_class.track_event(daily_event, values: entity1, time: 14.days.ago)
# Events 4 weeks ago
- described_class.track_event(entity3, daily_event, 28.days.ago)
- described_class.track_event(entity4, daily_event, 29.days.ago)
+ described_class.track_event(daily_event, values: entity3, time: 28.days.ago)
+ described_class.track_event(daily_event, values: entity4, time: 29.days.ago)
end
it 'raise error if metrics are not in the same slot' do
@@ -345,10 +353,10 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
allow(described_class).to receive(:known_events).and_return(known_events)
allow(described_class).to receive(:categories).and_return(%w(category1 category2))
- described_class.track_event_in_context([entity1, entity3], 'event_name_1', default_context, 2.days.ago)
- described_class.track_event_in_context(entity3, 'event_name_1', default_context, 2.days.ago)
- described_class.track_event_in_context(entity3, 'event_name_1', invalid_context, 2.days.ago)
- described_class.track_event_in_context([entity1, entity2], 'event_name_2', '', 2.weeks.ago)
+ described_class.track_event_in_context('event_name_1', values: [entity1, entity3], context: default_context, time: 2.days.ago)
+ described_class.track_event_in_context('event_name_1', values: entity3, context: default_context, time: 2.days.ago)
+ described_class.track_event_in_context('event_name_1', values: entity3, context: invalid_context, time: 2.days.ago)
+ described_class.track_event_in_context('event_name_2', values: [entity1, entity2], context: '', time: 2.weeks.ago)
end
subject(:unique_events) { described_class.unique_events(event_names: event_names, start_date: 4.weeks.ago, end_date: Date.current, context: context) }
@@ -386,13 +394,13 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
allow(described_class).to receive(:known_events).and_return(known_events)
allow(described_class).to receive(:categories).and_return(%w(category1 category2))
- described_class.track_event(entity1, 'event1_slot', 2.days.ago)
- described_class.track_event(entity2, 'event2_slot', 2.days.ago)
- described_class.track_event(entity3, 'event2_slot', 2.weeks.ago)
+ described_class.track_event('event1_slot', values: entity1, time: 2.days.ago)
+ described_class.track_event('event2_slot', values: entity2, time: 2.days.ago)
+ described_class.track_event('event2_slot', values: entity3, time: 2.weeks.ago)
# events in different slots
- described_class.track_event(entity2, 'event3', 2.days.ago)
- described_class.track_event(entity2, 'event4', 2.days.ago)
+ described_class.track_event('event3', values: entity2, time: 2.days.ago)
+ described_class.track_event('event4', values: entity2, time: 2.days.ago)
end
it 'returns the number of unique events for all known events' do
@@ -516,23 +524,23 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
subject(:aggregated_metrics_data) { described_class.aggregated_metrics_weekly_data }
before do
- described_class.track_event(entity1, 'event1_slot', 2.days.ago)
- described_class.track_event(entity2, 'event1_slot', 2.days.ago)
- described_class.track_event(entity3, 'event1_slot', 2.days.ago)
- described_class.track_event(entity1, 'event2_slot', 2.days.ago)
- described_class.track_event(entity2, 'event2_slot', 3.days.ago)
- described_class.track_event(entity3, 'event2_slot', 3.days.ago)
- described_class.track_event(entity1, 'event3_slot', 3.days.ago)
- described_class.track_event(entity2, 'event3_slot', 3.days.ago)
- described_class.track_event(entity2, 'event5_slot', 3.days.ago)
+ described_class.track_event('event1_slot', values: entity1, time: 2.days.ago)
+ described_class.track_event('event1_slot', values: entity2, time: 2.days.ago)
+ described_class.track_event('event1_slot', values: entity3, time: 2.days.ago)
+ described_class.track_event('event2_slot', values: entity1, time: 2.days.ago)
+ described_class.track_event('event2_slot', values: entity2, time: 3.days.ago)
+ described_class.track_event('event2_slot', values: entity3, time: 3.days.ago)
+ described_class.track_event('event3_slot', values: entity1, time: 3.days.ago)
+ described_class.track_event('event3_slot', values: entity2, time: 3.days.ago)
+ described_class.track_event('event5_slot', values: entity2, time: 3.days.ago)
# events out of time scope
- described_class.track_event(entity3, 'event2_slot', 8.days.ago)
+ described_class.track_event('event2_slot', values: entity3, time: 8.days.ago)
# events in different slots
- described_class.track_event(entity1, 'event4', 2.days.ago)
- described_class.track_event(entity2, 'event4', 2.days.ago)
- described_class.track_event(entity4, 'event4', 2.days.ago)
+ described_class.track_event('event4', values: entity1, time: 2.days.ago)
+ described_class.track_event('event4', values: entity2, time: 2.days.ago)
+ described_class.track_event('event4', values: entity4, time: 2.days.ago)
end
it_behaves_like 'aggregated_metrics_data'
@@ -543,23 +551,23 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
it_behaves_like 'aggregated_metrics_data' do
before do
- described_class.track_event(entity1, 'event1_slot', 2.days.ago)
- described_class.track_event(entity2, 'event1_slot', 2.days.ago)
- described_class.track_event(entity3, 'event1_slot', 2.days.ago)
- described_class.track_event(entity1, 'event2_slot', 2.days.ago)
- described_class.track_event(entity2, 'event2_slot', 3.days.ago)
- described_class.track_event(entity3, 'event2_slot', 3.days.ago)
- described_class.track_event(entity1, 'event3_slot', 3.days.ago)
- described_class.track_event(entity2, 'event3_slot', 10.days.ago)
- described_class.track_event(entity2, 'event5_slot', 4.weeks.ago.advance(days: 1))
+ described_class.track_event('event1_slot', values: entity1, time: 2.days.ago)
+ described_class.track_event('event1_slot', values: entity2, time: 2.days.ago)
+ described_class.track_event('event1_slot', values: entity3, time: 2.days.ago)
+ described_class.track_event('event2_slot', values: entity1, time: 2.days.ago)
+ described_class.track_event('event2_slot', values: entity2, time: 3.days.ago)
+ described_class.track_event('event2_slot', values: entity3, time: 3.days.ago)
+ described_class.track_event('event3_slot', values: entity1, time: 3.days.ago)
+ described_class.track_event('event3_slot', values: entity2, time: 10.days.ago)
+ described_class.track_event('event5_slot', values: entity2, time: 4.weeks.ago.advance(days: 1))
# events out of time scope
- described_class.track_event(entity1, 'event5_slot', 4.weeks.ago.advance(days: -1))
+ described_class.track_event('event5_slot', values: entity1, time: 4.weeks.ago.advance(days: -1))
# events in different slots
- described_class.track_event(entity1, 'event4', 2.days.ago)
- described_class.track_event(entity2, 'event4', 2.days.ago)
- described_class.track_event(entity4, 'event4', 2.days.ago)
+ described_class.track_event('event4', values: entity1, time: 2.days.ago)
+ described_class.track_event('event4', values: entity2, time: 2.days.ago)
+ described_class.track_event('event4', values: entity4, time: 2.days.ago)
end
end
diff --git a/spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb
new file mode 100644
index 00000000000..c7b208cfb31
--- /dev/null
+++ b/spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb
@@ -0,0 +1,151 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter, :clean_gitlab_redis_shared_state do
+ let(:merge_request) { build(:merge_request, id: 1) }
+ let(:user) { build(:user, id: 1) }
+ let(:note) { build(:note, author: user) }
+
+ shared_examples_for 'a tracked merge request unique event' do
+ specify do
+ expect { 3.times { subject } }
+ .to change {
+ Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(
+ event_names: action,
+ start_date: 2.weeks.ago,
+ end_date: 2.weeks.from_now
+ )
+ }
+ .by(1)
+ end
+ end
+
+ describe '.track_mr_diffs_action' do
+ subject { described_class.track_mr_diffs_action(merge_request: merge_request) }
+
+ it_behaves_like 'a tracked merge request unique event' do
+ let(:action) { described_class::MR_DIFFS_ACTION }
+ end
+ end
+
+ describe '.track_mr_diffs_single_file_action' do
+ subject { described_class.track_mr_diffs_single_file_action(merge_request: merge_request, user: user) }
+
+ it_behaves_like 'a tracked merge request unique event' do
+ let(:action) { described_class::MR_DIFFS_SINGLE_FILE_ACTION }
+ end
+
+ it_behaves_like 'a tracked merge request unique event' do
+ let(:action) { described_class::MR_DIFFS_USER_SINGLE_FILE_ACTION }
+ end
+ end
+
+ describe '.track_create_mr_action' do
+ subject { described_class.track_create_mr_action(user: user) }
+
+ it_behaves_like 'a tracked merge request unique event' do
+ let(:action) { described_class::MR_CREATE_ACTION }
+ end
+ end
+
+ describe '.track_close_mr_action' do
+ subject { described_class.track_close_mr_action(user: user) }
+
+ it_behaves_like 'a tracked merge request unique event' do
+ let(:action) { described_class::MR_CLOSE_ACTION }
+ end
+ end
+
+ describe '.track_merge_mr_action' do
+ subject { described_class.track_merge_mr_action(user: user) }
+
+ it_behaves_like 'a tracked merge request unique event' do
+ let(:action) { described_class::MR_MERGE_ACTION }
+ end
+ end
+
+ describe '.track_reopen_mr_action' do
+ subject { described_class.track_reopen_mr_action(user: user) }
+
+ it_behaves_like 'a tracked merge request unique event' do
+ let(:action) { described_class::MR_REOPEN_ACTION }
+ end
+ end
+
+ describe '.track_create_comment_action' do
+ subject { described_class.track_create_comment_action(note: note) }
+
+ it_behaves_like 'a tracked merge request unique event' do
+ let(:action) { described_class::MR_CREATE_COMMENT_ACTION }
+ end
+
+ context 'when the note is multiline diff note' do
+ let(:note) { build(:diff_note_on_merge_request, author: user) }
+
+ before do
+ allow(note).to receive(:multiline?).and_return(true)
+ end
+
+ it_behaves_like 'a tracked merge request unique event' do
+ let(:action) { described_class::MR_CREATE_MULTILINE_COMMENT_ACTION }
+ end
+ end
+ end
+
+ describe '.track_edit_comment_action' do
+ subject { described_class.track_edit_comment_action(note: note) }
+
+ it_behaves_like 'a tracked merge request unique event' do
+ let(:action) { described_class::MR_EDIT_COMMENT_ACTION }
+ end
+
+ context 'when the note is multiline diff note' do
+ let(:note) { build(:diff_note_on_merge_request, author: user) }
+
+ before do
+ allow(note).to receive(:multiline?).and_return(true)
+ end
+
+ it_behaves_like 'a tracked merge request unique event' do
+ let(:action) { described_class::MR_EDIT_MULTILINE_COMMENT_ACTION }
+ end
+ end
+ end
+
+ describe '.track_remove_comment_action' do
+ subject { described_class.track_remove_comment_action(note: note) }
+
+ it_behaves_like 'a tracked merge request unique event' do
+ let(:action) { described_class::MR_REMOVE_COMMENT_ACTION }
+ end
+
+ context 'when the note is multiline diff note' do
+ let(:note) { build(:diff_note_on_merge_request, author: user) }
+
+ before do
+ allow(note).to receive(:multiline?).and_return(true)
+ end
+
+ it_behaves_like 'a tracked merge request unique event' do
+ let(:action) { described_class::MR_REMOVE_MULTILINE_COMMENT_ACTION }
+ end
+ end
+ end
+
+ describe '.track_create_review_note_action' do
+ subject { described_class.track_create_review_note_action(user: user) }
+
+ it_behaves_like 'a tracked merge request unique event' do
+ let(:action) { described_class::MR_CREATE_REVIEW_NOTE_ACTION }
+ end
+ end
+
+ describe '.track_publish_review_action' do
+ subject { described_class.track_publish_review_action(user: user) }
+
+ it_behaves_like 'a tracked merge request unique event' do
+ let(:action) { described_class::MR_PUBLISH_REVIEW_ACTION }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/usage_data_counters/guest_package_event_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/package_event_counter_spec.rb
index d018100b041..7b5efb11034 100644
--- a/spec/lib/gitlab/usage_data_counters/guest_package_event_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/package_event_counter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::UsageDataCounters::GuestPackageEventCounter, :clean_gitlab_redis_shared_state do
+RSpec.describe Gitlab::UsageDataCounters::PackageEventCounter, :clean_gitlab_redis_shared_state do
shared_examples_for 'usage counter with totals' do |counter|
it 'increments counter and returns total count' do
expect(described_class.read(counter)).to eq(0)
@@ -14,7 +14,7 @@ RSpec.describe Gitlab::UsageDataCounters::GuestPackageEventCounter, :clean_gitla
end
it 'includes the right events' do
- expect(described_class::KNOWN_EVENTS.size).to eq 33
+ expect(described_class::KNOWN_EVENTS.size).to eq 45
end
described_class::KNOWN_EVENTS.each do |event|
@@ -24,8 +24,8 @@ RSpec.describe Gitlab::UsageDataCounters::GuestPackageEventCounter, :clean_gitla
describe '.fetch_supported_event' do
subject { described_class.fetch_supported_event(event_name) }
- let(:event_name) { 'package_guest_i_package_composer_guest_push' }
+ let(:event_name) { 'package_events_i_package_composer_push_package' }
- it { is_expected.to eq 'i_package_composer_guest_push' }
+ it { is_expected.to eq 'i_package_composer_push_package' }
end
end
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index 4d12bb6bd8c..fd02521622c 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -680,7 +680,9 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
it { is_expected.to include(:kubernetes_agent_gitops_sync) }
it { is_expected.to include(:static_site_editor_views) }
- it { is_expected.to include(:package_guest_i_package_composer_guest_pull) }
+ it { is_expected.to include(:package_events_i_package_pull_package) }
+ it { is_expected.to include(:package_events_i_package_delete_package_by_user) }
+ it { is_expected.to include(:package_events_i_package_conan_push_package) }
end
describe '.usage_data_counters' do
@@ -1260,7 +1262,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
subject { described_class.redis_hll_counters }
let(:categories) { ::Gitlab::UsageDataCounters::HLLRedisCounter.categories }
- let(:ineligible_total_categories) { %w[source_code testing ci_secrets_management incident_management_alerts snippets] }
+ let(:ineligible_total_categories) { %w[source_code ci_secrets_management incident_management_alerts snippets terraform] }
it 'has all known_events' do
expect(subject).to have_key(:redis_hll_counters)
diff --git a/spec/lib/gitlab/user_access_spec.rb b/spec/lib/gitlab/user_access_spec.rb
index 748a8336a25..97fff030906 100644
--- a/spec/lib/gitlab/user_access_spec.rb
+++ b/spec/lib/gitlab/user_access_spec.rb
@@ -45,10 +45,20 @@ RSpec.describe Gitlab::UserAccess do
let(:empty_project) { create(:project_empty_repo) }
let(:project_access) { described_class.new(user, container: empty_project) }
- it 'returns true for admins' do
- user.update!(admin: true)
+ context 'when admin mode is enabled', :enable_admin_mode do
+ it 'returns true for admins' do
+ user.update!(admin: true)
- expect(access.can_push_to_branch?('master')).to be_truthy
+ expect(access.can_push_to_branch?('master')).to be_truthy
+ end
+ end
+
+ context 'when admin mode is disabled' do
+ it 'returns false for admins' do
+ user.update!(admin: true)
+
+ expect(access.can_push_to_branch?('master')).to be_falsey
+ end
end
it 'returns true if user is maintainer' do
@@ -85,10 +95,20 @@ RSpec.describe Gitlab::UserAccess do
let(:branch) { create :protected_branch, project: project, name: "test" }
let(:not_existing_branch) { create :protected_branch, :developers_can_merge, project: project }
- it 'returns true for admins' do
- user.update!(admin: true)
+ context 'when admin mode is enabled', :enable_admin_mode do
+ it 'returns true for admins' do
+ user.update!(admin: true)
- expect(access.can_push_to_branch?(branch.name)).to be_truthy
+ expect(access.can_push_to_branch?(branch.name)).to be_truthy
+ end
+ end
+
+ context 'when admin mode is disabled' do
+ it 'returns false for admins' do
+ user.update!(admin: true)
+
+ expect(access.can_push_to_branch?(branch.name)).to be_falsey
+ end
end
it 'returns true if user is a maintainer' do
diff --git a/spec/lib/gitlab/utils/usage_data_spec.rb b/spec/lib/gitlab/utils/usage_data_spec.rb
index 521d6584a20..dfc381d0ef2 100644
--- a/spec/lib/gitlab/utils/usage_data_spec.rb
+++ b/spec/lib/gitlab/utils/usage_data_spec.rb
@@ -38,32 +38,116 @@ RSpec.describe Gitlab::Utils::UsageData do
end
describe '#estimate_batch_distinct_count' do
+ let(:error_rate) { Gitlab::Database::PostgresHll::BatchDistinctCounter::ERROR_RATE } # HyperLogLog is a probabilistic algorithm, which provides estimated data, with given error margin
let(:relation) { double(:relation) }
+ before do
+ allow(ActiveRecord::Base.connection).to receive(:transaction_open?).and_return(false)
+ end
+
it 'delegates counting to counter class instance' do
+ buckets = instance_double(Gitlab::Database::PostgresHll::Buckets)
+
expect_next_instance_of(Gitlab::Database::PostgresHll::BatchDistinctCounter, relation, 'column') do |instance|
- expect(instance).to receive(:estimate_distinct_count)
+ expect(instance).to receive(:execute)
.with(batch_size: nil, start: nil, finish: nil)
- .and_return(5)
+ .and_return(buckets)
end
+ expect(buckets).to receive(:estimated_distinct_count).and_return(5)
expect(described_class.estimate_batch_distinct_count(relation, 'column')).to eq(5)
end
- it 'returns default fallback value when counting fails due to database error' do
- stub_const("Gitlab::Utils::UsageData::FALLBACK", 15)
- allow(Gitlab::Database::PostgresHll::BatchDistinctCounter).to receive(:new).and_raise(ActiveRecord::StatementInvalid.new(''))
+ context 'quasi integration test for different counting parameters' do
+ # HyperLogLog http://algo.inria.fr/flajolet/Publications/FlFuGaMe07.pdf algorithm
+ # used in estimate_batch_distinct_count produce probabilistic
+ # estimations of unique values present in dataset, because of that its results
+ # are always off by some small factor from real value. However for given
+ # dataset it provide consistent and deterministic result. In the following context
+ # analyzed sets consist of values:
+ # build_needs set: ['1', '2', '3', '4', '5']
+ # ci_build set ['a', 'b']
+ # with them, current implementation is expected to consistently report
+ # 5.217656147118495 and 2.0809220082170614 values
+ # This test suite is expected to assure, that HyperLogLog implementation
+ # behaves consistently between changes made to other parts of codebase.
+ # In case of fine tuning or changes to HyperLogLog algorithm implementation
+ # one should run in depth analysis of accuracy with supplementary rake tasks
+ # currently under implementation at https://gitlab.com/gitlab-org/gitlab/-/merge_requests/51118
+ # and adjust used values in this context accordingly.
+ let_it_be(:build) { create(:ci_build, name: 'a') }
+ let_it_be(:another_build) { create(:ci_build, name: 'b') }
+
+ let(:model) { Ci::BuildNeed }
+ let(:column) { :name }
+ let(:build_needs_estimated_cardinality) { 5.217656147118495 }
+ let(:ci_builds_estimated_cardinality) { 2.0809220082170614 }
+
+ context 'different counting parameters' do
+ before_all do
+ 1.upto(3) { |i| create(:ci_build_need, name: i, build: build) }
+ 4.upto(5) { |i| create(:ci_build_need, name: i, build: another_build) }
+ end
+
+ it 'counts with symbol passed in column argument' do
+ expect(described_class.estimate_batch_distinct_count(model, column)).to eq(build_needs_estimated_cardinality)
+ end
+
+ it 'counts with string passed in column argument' do
+ expect(described_class.estimate_batch_distinct_count(model, column.to_s)).to eq(build_needs_estimated_cardinality)
+ end
+
+ it 'counts with table.column passed in column argument' do
+ expect(described_class.estimate_batch_distinct_count(model, "#{model.table_name}.#{column}")).to eq(build_needs_estimated_cardinality)
+ end
+
+ it 'counts with Arel passed in column argument' do
+ expect(described_class.estimate_batch_distinct_count(model, model.arel_table[column])).to eq(build_needs_estimated_cardinality)
+ end
+
+ it 'counts over joined relations' do
+ expect(described_class.estimate_batch_distinct_count(model.joins(:build), "ci_builds.name")).to eq(ci_builds_estimated_cardinality)
+ end
- expect(described_class.estimate_batch_distinct_count(relation)).to eq(15)
+ it 'counts with :column field with batch_size of 50K' do
+ expect(described_class.estimate_batch_distinct_count(model, column, batch_size: 50_000)).to eq(build_needs_estimated_cardinality)
+ end
+
+ it 'counts with different number of batches and aggregates total result' do
+ stub_const('Gitlab::Database::PostgresHll::BatchDistinctCounter::MIN_REQUIRED_BATCH_SIZE', 0)
+
+ [1, 2, 4, 5, 6].each { |i| expect(described_class.estimate_batch_distinct_count(model, column, batch_size: i)).to eq(build_needs_estimated_cardinality) }
+ end
+
+ it 'counts with a start and finish' do
+ expect(described_class.estimate_batch_distinct_count(model, column, start: model.minimum(:id), finish: model.maximum(:id))).to eq(build_needs_estimated_cardinality)
+ end
+ end
end
- it 'logs error and returns DISTRIBUTED_HLL_FALLBACK value when counting raises any error', :aggregate_failures do
- error = StandardError.new('')
- stub_const("Gitlab::Utils::UsageData::DISTRIBUTED_HLL_FALLBACK", 15)
- allow(Gitlab::Database::PostgresHll::BatchDistinctCounter).to receive(:new).and_raise(error)
+ describe 'error handling' do
+ before do
+ stub_const("Gitlab::Utils::UsageData::FALLBACK", 3)
+ stub_const("Gitlab::Utils::UsageData::DISTRIBUTED_HLL_FALLBACK", 4)
+ end
+
+ it 'returns fallback if counter raises WRONG_CONFIGURATION_ERROR' do
+ expect(described_class.estimate_batch_distinct_count(relation, 'id', start: 1, finish: 0)).to eq 3
+ end
+
+ it 'returns default fallback value when counting fails due to database error' do
+ allow(Gitlab::Database::PostgresHll::BatchDistinctCounter).to receive(:new).and_raise(ActiveRecord::StatementInvalid.new(''))
- expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).with(error)
- expect(described_class.estimate_batch_distinct_count(relation)).to eq(15)
+ expect(described_class.estimate_batch_distinct_count(relation)).to eq(3)
+ end
+
+ it 'logs error and returns DISTRIBUTED_HLL_FALLBACK value when counting raises any error', :aggregate_failures do
+ error = StandardError.new('')
+ allow(Gitlab::Database::PostgresHll::BatchDistinctCounter).to receive(:new).and_raise(error)
+
+ expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).with(error)
+ expect(described_class.estimate_batch_distinct_count(relation)).to eq(4)
+ end
end
end
@@ -193,7 +277,7 @@ RSpec.describe Gitlab::Utils::UsageData do
context 'when Prometheus server address is available from settings' do
before do
expect(Gitlab::Prometheus::Internal).to receive(:prometheus_enabled?).and_return(true)
- expect(Gitlab::Prometheus::Internal).to receive(:server_address).and_return('prom:9090')
+ expect(Gitlab::Prometheus::Internal).to receive(:uri).and_return('http://prom:9090')
end
it_behaves_like 'try to query Prometheus with given address'
@@ -256,7 +340,7 @@ RSpec.describe Gitlab::Utils::UsageData do
end
it 'tracks redis hll event' do
- expect(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:track_event).with(value, event_name)
+ expect(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:track_event).with(event_name, values: value)
described_class.track_usage_event(event_name, value)
end
diff --git a/spec/lib/gitlab/utils_spec.rb b/spec/lib/gitlab/utils_spec.rb
index 36257a0605b..1052d4cbacc 100644
--- a/spec/lib/gitlab/utils_spec.rb
+++ b/spec/lib/gitlab/utils_spec.rb
@@ -392,6 +392,23 @@ RSpec.describe Gitlab::Utils do
end
end
+ describe ".safe_downcase!" do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:str, :result) do
+ "test".freeze | "test"
+ "Test".freeze | "test"
+ "test" | "test"
+ "Test" | "test"
+ end
+
+ with_them do
+ it "downcases the string" do
+ expect(described_class.safe_downcase!(str)).to eq(result)
+ end
+ end
+ end
+
describe '.parse_url' do
it 'returns Addressable::URI object' do
expect(described_class.parse_url('http://gitlab.com')).to be_instance_of(Addressable::URI)
diff --git a/spec/lib/gitlab/uuid_spec.rb b/spec/lib/gitlab/uuid_spec.rb
index a2e28f5a24d..44c1d30fce0 100644
--- a/spec/lib/gitlab/uuid_spec.rb
+++ b/spec/lib/gitlab/uuid_spec.rb
@@ -49,4 +49,23 @@ RSpec.describe Gitlab::UUID do
it { is_expected.to eq(production_proper_uuid) }
end
end
+
+ describe 'v5?' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:test_string, :is_uuid_v5) do
+ 'not even a uuid' | false
+ 'this-seems-like-a-uuid' | false
+ 'thislook-more-5lik-eava-liduuidbutno' | false
+ '9f470438-db0f-37b7-9ca9-1d47104c339a' | false
+ '9f470438-db0f-47b7-9ca9-1d47104c339a' | false
+ '9f470438-db0f-57b7-9ca9-1d47104c339a' | true
+ end
+
+ with_them do
+ subject { described_class.v5?(test_string) }
+
+ it { is_expected.to be(is_uuid_v5) }
+ end
+ end
end
diff --git a/spec/lib/gitlab/visibility_level_spec.rb b/spec/lib/gitlab/visibility_level_spec.rb
index 2ac343cd1e7..63c31c82d59 100644
--- a/spec/lib/gitlab/visibility_level_spec.rb
+++ b/spec/lib/gitlab/visibility_level_spec.rb
@@ -22,13 +22,25 @@ RSpec.describe Gitlab::VisibilityLevel do
end
describe '.levels_for_user' do
- it 'returns all levels for an admin' do
- user = build(:user, :admin)
+ context 'when admin mode is enabled', :enable_admin_mode do
+ it 'returns all levels for an admin' do
+ user = build(:user, :admin)
+
+ expect(described_class.levels_for_user(user))
+ .to eq([Gitlab::VisibilityLevel::PRIVATE,
+ Gitlab::VisibilityLevel::INTERNAL,
+ Gitlab::VisibilityLevel::PUBLIC])
+ end
+ end
- expect(described_class.levels_for_user(user))
- .to eq([Gitlab::VisibilityLevel::PRIVATE,
- Gitlab::VisibilityLevel::INTERNAL,
- Gitlab::VisibilityLevel::PUBLIC])
+ context 'when admin mode is disabled' do
+ it 'returns INTERNAL and PUBLIC for an admin' do
+ user = build(:user, :admin)
+
+ expect(described_class.levels_for_user(user))
+ .to eq([Gitlab::VisibilityLevel::INTERNAL,
+ Gitlab::VisibilityLevel::PUBLIC])
+ end
end
it 'returns INTERNAL and PUBLIC for internal users' do
@@ -119,28 +131,4 @@ RSpec.describe Gitlab::VisibilityLevel do
end
end
end
-
- describe '#visibility_level_decreased?' do
- let(:project) { create(:project, :internal) }
-
- context 'when visibility level decreases' do
- before do
- project.update!(visibility_level: described_class::PRIVATE)
- end
-
- it 'returns true' do
- expect(project.visibility_level_decreased?).to be(true)
- end
- end
-
- context 'when visibility level does not decrease' do
- before do
- project.update!(visibility_level: described_class::PUBLIC)
- end
-
- it 'returns false' do
- expect(project.visibility_level_decreased?).to be(false)
- end
- end
- end
end