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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-02-05 19:20:45 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-02-05 19:20:45 +0300
commitd298fad0c0564454271cba11e6f20c19681534ac (patch)
tree0a19d07d8b3bdd2574617305c300e404f2ace581 /spec/lib/gitlab
parentc9f9eec79cab801a50db698f682aacffbedf07f7 (diff)
Add latest changes from gitlab-org/gitlab@13-9-stable-eev13.9.0-rc41
Diffstat (limited to 'spec/lib/gitlab')
-rw-r--r--spec/lib/gitlab/access/branch_protection_spec.rb10
-rw-r--r--spec/lib/gitlab/auth/ip_rate_limiter_spec.rb3
-rw-r--r--spec/lib/gitlab/auth/u2f_webauthn_converter_spec.rb29
-rw-r--r--spec/lib/gitlab/background_migration/remove_duplicate_vulnerabilities_findings_spec.rb135
-rw-r--r--spec/lib/gitlab/background_migration_spec.rb2
-rw-r--r--spec/lib/gitlab/changelog/committer_spec.rb128
-rw-r--r--spec/lib/gitlab/changelog/config_spec.rb96
-rw-r--r--spec/lib/gitlab/changelog/generator_spec.rb164
-rw-r--r--spec/lib/gitlab/changelog/release_spec.rb107
-rw-r--r--spec/lib/gitlab/changelog/template/compiler_spec.rb136
-rw-r--r--spec/lib/gitlab/ci/badge/coverage/metadata_spec.rb (renamed from spec/lib/gitlab/badge/coverage/metadata_spec.rb)4
-rw-r--r--spec/lib/gitlab/ci/badge/coverage/report_spec.rb (renamed from spec/lib/gitlab/badge/coverage/report_spec.rb)2
-rw-r--r--spec/lib/gitlab/ci/badge/coverage/template_spec.rb (renamed from spec/lib/gitlab/badge/coverage/template_spec.rb)2
-rw-r--r--spec/lib/gitlab/ci/badge/pipeline/metadata_spec.rb (renamed from spec/lib/gitlab/badge/pipeline/metadata_spec.rb)4
-rw-r--r--spec/lib/gitlab/ci/badge/pipeline/status_spec.rb (renamed from spec/lib/gitlab/badge/pipeline/status_spec.rb)2
-rw-r--r--spec/lib/gitlab/ci/badge/pipeline/template_spec.rb (renamed from spec/lib/gitlab/badge/pipeline/template_spec.rb)2
-rw-r--r--spec/lib/gitlab/ci/badge/shared/metadata.rb (renamed from spec/lib/gitlab/badge/shared/metadata.rb)0
-rw-r--r--spec/lib/gitlab/ci/build/credentials/registry/dependency_proxy_spec.rb43
-rw-r--r--spec/lib/gitlab/ci/build/credentials/registry/gitlab_registry_spec.rb (renamed from spec/lib/gitlab/ci/build/credentials/registry_spec.rb)2
-rw-r--r--spec/lib/gitlab/ci/build/rules_spec.rb29
-rw-r--r--spec/lib/gitlab/ci/charts_spec.rb82
-rw-r--r--spec/lib/gitlab/ci/config/entry/cache_spec.rb10
-rw-r--r--spec/lib/gitlab/ci/config/entry/job_spec.rb10
-rw-r--r--spec/lib/gitlab/ci/config/entry/processable_spec.rb29
-rw-r--r--spec/lib/gitlab/ci/config/external/mapper_spec.rb14
-rw-r--r--spec/lib/gitlab/ci/cron_parser_spec.rb11
-rw-r--r--spec/lib/gitlab/ci/parsers/instrumentation_spec.rb27
-rw-r--r--spec/lib/gitlab/ci/parsers_spec.rb8
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/cancel_pending_pipelines_spec.rb14
-rw-r--r--spec/lib/gitlab/ci/reports/codequality_mr_diff_spec.rb58
-rw-r--r--spec/lib/gitlab/ci/reports/codequality_reports_comparer_spec.rb58
-rw-r--r--spec/lib/gitlab/ci/reports/codequality_reports_spec.rb58
-rw-r--r--spec/lib/gitlab/ci/trace/chunked_io_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/variables/helpers_spec.rb103
-rw-r--r--spec/lib/gitlab/cleanup/orphan_job_artifact_files_spec.rb3
-rw-r--r--spec/lib/gitlab/cluster/lifecycle_events_spec.rb85
-rw-r--r--spec/lib/gitlab/composer/cache_spec.rb133
-rw-r--r--spec/lib/gitlab/composer/version_index_spec.rb16
-rw-r--r--spec/lib/gitlab/conan_token_spec.rb2
-rw-r--r--spec/lib/gitlab/crypto_helper_spec.rb78
-rw-r--r--spec/lib/gitlab/current_settings_spec.rb28
-rw-r--r--spec/lib/gitlab/danger/base_linter_spec.rb193
-rw-r--r--spec/lib/gitlab/danger/changelog_spec.rb229
-rw-r--r--spec/lib/gitlab/danger/commit_linter_spec.rb242
-rw-r--r--spec/lib/gitlab/danger/danger_spec_helper.rb17
-rw-r--r--spec/lib/gitlab/danger/emoji_checker_spec.rb38
-rw-r--r--spec/lib/gitlab/danger/helper_spec.rb602
-rw-r--r--spec/lib/gitlab/danger/merge_request_linter_spec.rb55
-rw-r--r--spec/lib/gitlab/danger/roulette_spec.rb413
-rw-r--r--spec/lib/gitlab/danger/sidekiq_queues_spec.rb82
-rw-r--r--spec/lib/gitlab/danger/teammate_spec.rb220
-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.rb4
-rw-r--r--spec/lib/gitlab/data_builder/pipeline_spec.rb4
-rw-r--r--spec/lib/gitlab/database/migration_helpers/v2_spec.rb221
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb253
-rw-r--r--spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb3
-rw-r--r--spec/lib/gitlab/database/with_lock_retries_spec.rb4
-rw-r--r--spec/lib/gitlab/diff/char_diff_spec.rb77
-rw-r--r--spec/lib/gitlab/diff/file_collection_sorter_spec.rb10
-rw-r--r--spec/lib/gitlab/diff/highlight_cache_spec.rb18
-rw-r--r--spec/lib/gitlab/diff/inline_diff_spec.rb27
-rw-r--r--spec/lib/gitlab/experimentation/controller_concern_spec.rb27
-rw-r--r--spec/lib/gitlab/experimentation/experiment_spec.rb3
-rw-r--r--spec/lib/gitlab/experimentation_spec.rb65
-rw-r--r--spec/lib/gitlab/file_finder_spec.rb8
-rw-r--r--spec/lib/gitlab/git/commit_spec.rb3
-rw-r--r--spec/lib/gitlab/git/diff_spec.rb7
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb5
-rw-r--r--spec/lib/gitlab/graphql/pagination/connections_spec.rb97
-rw-r--r--spec/lib/gitlab/hook_data/group_builder_spec.rb68
-rw-r--r--spec/lib/gitlab/hook_data/subgroup_builder_spec.rb52
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml5
-rw-r--r--spec/lib/gitlab/import_export/design_repo_restorer_spec.rb4
-rw-r--r--spec/lib/gitlab/import_export/design_repo_saver_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/fork_spec.rb4
-rw-r--r--spec/lib/gitlab/import_export/group/tree_restorer_spec.rb1
-rw-r--r--spec/lib/gitlab/import_export/importer_spec.rb4
-rw-r--r--spec/lib/gitlab/import_export/repo_restorer_spec.rb10
-rw-r--r--spec/lib/gitlab/import_export/repo_saver_spec.rb10
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml4
-rw-r--r--spec/lib/gitlab/import_export/saver_spec.rb12
-rw-r--r--spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb2
-rw-r--r--spec/lib/gitlab/instrumentation/redis_cluster_validator_spec.rb1
-rw-r--r--spec/lib/gitlab/instrumentation_helper_spec.rb6
-rw-r--r--spec/lib/gitlab/kas_spec.rb44
-rw-r--r--spec/lib/gitlab/metrics/subscribers/external_http_spec.rb172
-rw-r--r--spec/lib/gitlab/metrics/subscribers/rack_attack_spec.rb203
-rw-r--r--spec/lib/gitlab/patch/prependable_spec.rb18
-rw-r--r--spec/lib/gitlab/performance_bar/stats_spec.rb8
-rw-r--r--spec/lib/gitlab/rack_attack/instrumented_cache_store_spec.rb89
-rw-r--r--spec/lib/gitlab/rack_attack_spec.rb3
-rw-r--r--spec/lib/gitlab/repository_cache_adapter_spec.rb7
-rw-r--r--spec/lib/gitlab/search/query_spec.rb18
-rw-r--r--spec/lib/gitlab/search_results_spec.rb7
-rw-r--r--spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb3
-rw-r--r--spec/lib/gitlab/suggestions/commit_message_spec.rb11
-rw-r--r--spec/lib/gitlab/template/finders/global_template_finder_spec.rb23
-rw-r--r--spec/lib/gitlab/terraform/state_migration_helper_spec.rb21
-rw-r--r--spec/lib/gitlab/tracking/standard_context_spec.rb50
-rw-r--r--spec/lib/gitlab/url_blocker_spec.rb46
-rw-r--r--spec/lib/gitlab/url_blockers/url_allowlist_spec.rb28
-rw-r--r--spec/lib/gitlab/usage/docs/renderer_spec.rb21
-rw-r--r--spec/lib/gitlab/usage/docs/value_formatter_spec.rb26
-rw-r--r--spec/lib/gitlab/usage/metric_definition_spec.rb23
-rw-r--r--spec/lib/gitlab/usage/metric_spec.rb8
-rw-r--r--spec/lib/gitlab/usage/metrics/aggregates/aggregate_spec.rb256
-rw-r--r--spec/lib/gitlab/usage_data_counters/aggregated_metrics_spec.rb4
-rw-r--r--spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb195
-rw-r--r--spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb48
-rw-r--r--spec/lib/gitlab/usage_data_counters/quick_action_activity_unique_counter_spec.rb163
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb144
-rw-r--r--spec/lib/gitlab/utils/markdown_spec.rb44
-rw-r--r--spec/lib/gitlab/utils/override_spec.rb67
-rw-r--r--spec/lib/gitlab/utils/usage_data_spec.rb103
-rw-r--r--spec/lib/gitlab/utils_spec.rb8
118 files changed, 3803 insertions, 3052 deletions
diff --git a/spec/lib/gitlab/access/branch_protection_spec.rb b/spec/lib/gitlab/access/branch_protection_spec.rb
index 9b736a30c7e..44c30d1f596 100644
--- a/spec/lib/gitlab/access/branch_protection_spec.rb
+++ b/spec/lib/gitlab/access/branch_protection_spec.rb
@@ -3,9 +3,9 @@
require 'spec_helper'
RSpec.describe Gitlab::Access::BranchProtection do
- describe '#any?' do
- using RSpec::Parameterized::TableSyntax
+ using RSpec::Parameterized::TableSyntax
+ describe '#any?' do
where(:level, :result) do
Gitlab::Access::PROTECTION_NONE | false
Gitlab::Access::PROTECTION_DEV_CAN_PUSH | true
@@ -19,8 +19,6 @@ RSpec.describe Gitlab::Access::BranchProtection do
end
describe '#developer_can_push?' do
- using RSpec::Parameterized::TableSyntax
-
where(:level, :result) do
Gitlab::Access::PROTECTION_NONE | false
Gitlab::Access::PROTECTION_DEV_CAN_PUSH | true
@@ -36,8 +34,6 @@ RSpec.describe Gitlab::Access::BranchProtection do
end
describe '#developer_can_merge?' do
- using RSpec::Parameterized::TableSyntax
-
where(:level, :result) do
Gitlab::Access::PROTECTION_NONE | false
Gitlab::Access::PROTECTION_DEV_CAN_PUSH | false
@@ -53,8 +49,6 @@ RSpec.describe Gitlab::Access::BranchProtection do
end
describe '#fully_protected?' do
- using RSpec::Parameterized::TableSyntax
-
where(:level, :result) do
Gitlab::Access::PROTECTION_NONE | false
Gitlab::Access::PROTECTION_DEV_CAN_PUSH | false
diff --git a/spec/lib/gitlab/auth/ip_rate_limiter_spec.rb b/spec/lib/gitlab/auth/ip_rate_limiter_spec.rb
index 3d782272d7e..f23fdd3fbcb 100644
--- a/spec/lib/gitlab/auth/ip_rate_limiter_spec.rb
+++ b/spec/lib/gitlab/auth/ip_rate_limiter_spec.rb
@@ -19,6 +19,9 @@ RSpec.describe Gitlab::Auth::IpRateLimiter, :use_clean_rails_memory_store_cachin
before do
stub_rack_attack_setting(options)
+ Rack::Attack.reset!
+ Rack::Attack.clear_configuration
+ Gitlab::RackAttack.configure(Rack::Attack)
end
after do
diff --git a/spec/lib/gitlab/auth/u2f_webauthn_converter_spec.rb b/spec/lib/gitlab/auth/u2f_webauthn_converter_spec.rb
new file mode 100644
index 00000000000..deddc7f5294
--- /dev/null
+++ b/spec/lib/gitlab/auth/u2f_webauthn_converter_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Auth::U2fWebauthnConverter do
+ let_it_be(:u2f_registration) do
+ device = U2F::FakeU2F.new(FFaker::BaconIpsum.characters(5))
+ create(:u2f_registration, name: 'u2f_device',
+ certificate: Base64.strict_encode64(device.cert_raw),
+ key_handle: U2F.urlsafe_encode64(device.key_handle_raw),
+ public_key: Base64.strict_encode64(device.origin_public_key_raw))
+ end
+
+ it 'converts u2f registration' do
+ webauthn_credential = WebAuthn::U2fMigrator.new(
+ app_id: Gitlab.config.gitlab.url,
+ certificate: u2f_registration.certificate,
+ key_handle: u2f_registration.key_handle,
+ public_key: u2f_registration.public_key,
+ counter: u2f_registration.counter
+ ).credential
+
+ converted_webauthn = described_class.new(u2f_registration).convert
+
+ expect(converted_webauthn).to(
+ include(user_id: u2f_registration.user_id,
+ credential_xid: Base64.strict_encode64(webauthn_credential.id)))
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/remove_duplicate_vulnerabilities_findings_spec.rb b/spec/lib/gitlab/background_migration/remove_duplicate_vulnerabilities_findings_spec.rb
new file mode 100644
index 00000000000..47e1d4620cd
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/remove_duplicate_vulnerabilities_findings_spec.rb
@@ -0,0 +1,135 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::RemoveDuplicateVulnerabilitiesFindings do
+ let(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') }
+ let(:users) { table(:users) }
+ let(:user) { create_user! }
+ let(:project) { table(:projects).create!(id: 123, namespace_id: namespace.id) }
+ let(:scanners) { table(:vulnerability_scanners) }
+ let!(:scanner) { scanners.create!(project_id: project.id, external_id: 'test 1', name: 'test scanner 1') }
+ let!(:scanner2) { scanners.create!(project_id: project.id, external_id: 'test 2', name: 'test scanner 2') }
+ let!(:scanner3) { scanners.create!(project_id: project.id, external_id: 'test 3', name: 'test scanner 3') }
+ let!(:unrelated_scanner) { scanners.create!(project_id: project.id, external_id: 'unreleated_scanner', name: 'unrelated scanner') }
+ let(:vulnerabilities) { table(:vulnerabilities) }
+ let(:vulnerability_findings) { table(:vulnerability_occurrences) }
+ let(:vulnerability_identifiers) { table(:vulnerability_identifiers) }
+ let(:vulnerability_identifier) do
+ vulnerability_identifiers.create!(
+ project_id: project.id,
+ external_type: 'vulnerability-identifier',
+ external_id: 'vulnerability-identifier',
+ fingerprint: '7e394d1b1eb461a7406d7b1e08f057a1cf11287a',
+ name: 'vulnerability identifier')
+ end
+
+ let!(:first_finding) do
+ create_finding!(
+ uuid: "test1",
+ vulnerability_id: nil,
+ report_type: 0,
+ location_fingerprint: '2bda3014914481791847d8eca38d1a8d13b6ad76',
+ primary_identifier_id: vulnerability_identifier.id,
+ scanner_id: scanner.id,
+ project_id: project.id
+ )
+ end
+
+ let!(:first_duplicate) do
+ create_finding!(
+ uuid: "test2",
+ vulnerability_id: nil,
+ report_type: 0,
+ location_fingerprint: '2bda3014914481791847d8eca38d1a8d13b6ad76',
+ primary_identifier_id: vulnerability_identifier.id,
+ scanner_id: scanner2.id,
+ project_id: project.id
+ )
+ end
+
+ let!(:second_duplicate) do
+ create_finding!(
+ uuid: "test3",
+ vulnerability_id: nil,
+ report_type: 0,
+ location_fingerprint: '2bda3014914481791847d8eca38d1a8d13b6ad76',
+ primary_identifier_id: vulnerability_identifier.id,
+ scanner_id: scanner3.id,
+ project_id: project.id
+ )
+ end
+
+ let!(:unrelated_finding) do
+ create_finding!(
+ uuid: "unreleated_finding",
+ vulnerability_id: nil,
+ report_type: 1,
+ location_fingerprint: 'random_location_fingerprint',
+ primary_identifier_id: vulnerability_identifier.id,
+ scanner_id: unrelated_scanner.id,
+ project_id: project.id
+ )
+ end
+
+ subject { described_class.new.perform(first_finding.id, unrelated_finding.id) }
+
+ before do
+ stub_const("#{described_class}::DELETE_BATCH_SIZE", 1)
+ end
+
+ it "removes entries which would result in duplicate UUIDv5" do
+ expect(vulnerability_findings.count).to eq(4)
+
+ expect { subject }.to change { vulnerability_findings.count }.from(4).to(2)
+
+ expect(vulnerability_findings.pluck(:id)).to eq([second_duplicate.id, unrelated_finding.id])
+ end
+
+ private
+
+ def create_vulnerability!(project_id:, author_id:, title: 'test', severity: 7, confidence: 7, report_type: 0)
+ vulnerabilities.create!(
+ project_id: project_id,
+ author_id: author_id,
+ title: title,
+ severity: severity,
+ confidence: confidence,
+ report_type: report_type
+ )
+ end
+
+ # rubocop:disable Metrics/ParameterLists
+ def create_finding!(
+ vulnerability_id:, project_id:, scanner_id:, primary_identifier_id:,
+ name: "test", severity: 7, confidence: 7, report_type: 0,
+ project_fingerprint: '123qweasdzxc', location_fingerprint: 'test',
+ metadata_version: 'test', raw_metadata: 'test', uuid: 'test')
+ vulnerability_findings.create!(
+ vulnerability_id: vulnerability_id,
+ project_id: project_id,
+ name: name,
+ severity: severity,
+ confidence: confidence,
+ report_type: report_type,
+ project_fingerprint: project_fingerprint,
+ scanner_id: scanner_id,
+ primary_identifier_id: vulnerability_identifier.id,
+ location_fingerprint: location_fingerprint,
+ metadata_version: metadata_version,
+ raw_metadata: raw_metadata,
+ uuid: uuid
+ )
+ end
+ # rubocop:enable Metrics/ParameterLists
+
+ def create_user!(name: "Example User", email: "user@example.com", user_type: nil, created_at: Time.zone.now, confirmed_at: Time.zone.now)
+ users.create!(
+ name: name,
+ email: email,
+ username: name,
+ projects_limit: 0,
+ user_type: user_type,
+ confirmed_at: confirmed_at
+ )
+ end
+end
diff --git a/spec/lib/gitlab/background_migration_spec.rb b/spec/lib/gitlab/background_migration_spec.rb
index 052a01a8dd8..5b20572578c 100644
--- a/spec/lib/gitlab/background_migration_spec.rb
+++ b/spec/lib/gitlab/background_migration_spec.rb
@@ -55,7 +55,7 @@ RSpec.describe Gitlab::BackgroundMigration do
expect(described_class).to receive(:perform)
.with('Foo', [10, 20])
- described_class.steal('Foo') { |(arg1, arg2)| arg1 == 10 && arg2 == 20 }
+ described_class.steal('Foo') { |job| job.args.second.first == 10 && job.args.second.second == 20 }
end
it 'does not steal jobs that do not match the predicate' do
diff --git a/spec/lib/gitlab/changelog/committer_spec.rb b/spec/lib/gitlab/changelog/committer_spec.rb
new file mode 100644
index 00000000000..f0d6bc2b6b5
--- /dev/null
+++ b/spec/lib/gitlab/changelog/committer_spec.rb
@@ -0,0 +1,128 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Changelog::Committer do
+ let(:project) { create(:project, :repository) }
+ let(:user) { project.creator }
+ let(:committer) { described_class.new(project, user) }
+ let(:config) { Gitlab::Changelog::Config.new(project) }
+
+ describe '#commit' do
+ context "when the release isn't in the changelog" do
+ it 'commits the changes' do
+ release = Gitlab::Changelog::Release
+ .new(version: '1.0.0', date: Time.utc(2020, 1, 1), config: config)
+
+ committer.commit(
+ release: release,
+ file: 'CHANGELOG.md',
+ branch: 'master',
+ message: 'Test commit'
+ )
+
+ content = project.repository.blob_at('master', 'CHANGELOG.md').data
+
+ expect(content).to eq(<<~MARKDOWN)
+ ## 1.0.0 (2020-01-01)
+
+ No changes.
+ MARKDOWN
+ end
+ end
+
+ context 'when the release is already in the changelog' do
+ it "doesn't commit the changes" do
+ release = Gitlab::Changelog::Release
+ .new(version: '1.0.0', date: Time.utc(2020, 1, 1), config: config)
+
+ 2.times do
+ committer.commit(
+ release: release,
+ file: 'CHANGELOG.md',
+ branch: 'master',
+ message: 'Test commit'
+ )
+ end
+
+ content = project.repository.blob_at('master', 'CHANGELOG.md').data
+
+ expect(content).to eq(<<~MARKDOWN)
+ ## 1.0.0 (2020-01-01)
+
+ No changes.
+ MARKDOWN
+ end
+ end
+
+ context 'when committing the changes fails' do
+ it 'retries the operation' do
+ release = Gitlab::Changelog::Release
+ .new(version: '1.0.0', date: Time.utc(2020, 1, 1), config: config)
+
+ service = instance_spy(Files::MultiService)
+ errored = false
+
+ allow(Files::MultiService)
+ .to receive(:new)
+ .and_return(service)
+
+ allow(service).to receive(:execute) do
+ if errored
+ { status: :success }
+ else
+ errored = true
+ { status: :error }
+ end
+ end
+
+ expect do
+ committer.commit(
+ release: release,
+ file: 'CHANGELOG.md',
+ branch: 'master',
+ message: 'Test commit'
+ )
+ end.not_to raise_error
+ end
+ end
+
+ context "when the changelog changes before saving the changes" do
+ it 'raises a CommitError' do
+ release1 = Gitlab::Changelog::Release
+ .new(version: '1.0.0', date: Time.utc(2020, 1, 1), config: config)
+
+ release2 = Gitlab::Changelog::Release
+ .new(version: '2.0.0', date: Time.utc(2020, 1, 1), config: config)
+
+ # This creates the initial commit we'll later use to see if the
+ # changelog changed before saving our changes.
+ committer.commit(
+ release: release1,
+ file: 'CHANGELOG.md',
+ branch: 'master',
+ message: 'Initial commit'
+ )
+
+ allow(Gitlab::Git::Commit)
+ .to receive(:last_for_path)
+ .with(
+ project.repository,
+ 'master',
+ 'CHANGELOG.md',
+ literal_pathspec: true
+ )
+ .and_return(double(:commit, sha: 'foo'))
+
+ expect do
+ committer.commit(
+ release: release2,
+ file: 'CHANGELOG.md',
+ branch: 'master',
+ message: 'Test commit'
+ )
+ end.to raise_error(described_class::CommitError)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/changelog/config_spec.rb b/spec/lib/gitlab/changelog/config_spec.rb
new file mode 100644
index 00000000000..adf82fa3ac2
--- /dev/null
+++ b/spec/lib/gitlab/changelog/config_spec.rb
@@ -0,0 +1,96 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Changelog::Config do
+ let(:project) { build_stubbed(:project) }
+
+ describe '.from_git' do
+ it 'retrieves the configuration from Git' do
+ allow(project.repository)
+ .to receive(:changelog_config)
+ .and_return("---\ndate_format: '%Y'")
+
+ expect(described_class)
+ .to receive(:from_hash)
+ .with(project, 'date_format' => '%Y')
+
+ described_class.from_git(project)
+ end
+
+ it 'returns the default configuration when no YAML file exists in Git' do
+ allow(project.repository)
+ .to receive(:changelog_config)
+ .and_return(nil)
+
+ expect(described_class)
+ .to receive(:new)
+ .with(project)
+
+ described_class.from_git(project)
+ end
+ end
+
+ describe '.from_hash' do
+ it 'sets the configuration according to a Hash' do
+ config = described_class.from_hash(
+ project,
+ 'date_format' => 'foo',
+ 'template' => 'bar',
+ 'categories' => { 'foo' => 'bar' }
+ )
+
+ expect(config.date_format).to eq('foo')
+ expect(config.template).to be_instance_of(Gitlab::Changelog::Template::Template)
+ expect(config.categories).to eq({ 'foo' => 'bar' })
+ end
+
+ it 'raises ConfigError when the categories are not a Hash' do
+ expect { described_class.from_hash(project, 'categories' => 10) }
+ .to raise_error(described_class::ConfigError)
+ end
+ end
+
+ describe '#contributor?' do
+ it 'returns true if a user is a contributor' do
+ user = build_stubbed(:author)
+
+ allow(project.team).to receive(:contributor?).with(user).and_return(true)
+
+ expect(described_class.new(project).contributor?(user)).to eq(true)
+ end
+
+ it "returns true if a user isn't a contributor" do
+ user = build_stubbed(:author)
+
+ allow(project.team).to receive(:contributor?).with(user).and_return(false)
+
+ expect(described_class.new(project).contributor?(user)).to eq(false)
+ end
+ end
+
+ describe '#category' do
+ it 'returns the name of a category' do
+ config = described_class.new(project)
+
+ config.categories['foo'] = 'Foo'
+
+ expect(config.category('foo')).to eq('Foo')
+ end
+
+ it 'returns the raw category name when no alternative name is configured' do
+ config = described_class.new(project)
+
+ expect(config.category('bla')).to eq('bla')
+ end
+ end
+
+ describe '#format_date' do
+ it 'formats a date according to the configured date format' do
+ config = described_class.new(project)
+ time = Time.utc(2021, 1, 5)
+
+ expect(config.format_date(time)).to eq('2021-01-05')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/changelog/generator_spec.rb b/spec/lib/gitlab/changelog/generator_spec.rb
new file mode 100644
index 00000000000..bc4a7c5dd6b
--- /dev/null
+++ b/spec/lib/gitlab/changelog/generator_spec.rb
@@ -0,0 +1,164 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Changelog::Generator do
+ describe '#add' do
+ let(:project) { build_stubbed(:project) }
+ let(:author) { build_stubbed(:user) }
+ let(:commit) { build_stubbed(:commit) }
+ let(:config) { Gitlab::Changelog::Config.new(project) }
+
+ it 'generates the Markdown for the first release' do
+ release = Gitlab::Changelog::Release.new(
+ version: '1.0.0',
+ date: Time.utc(2021, 1, 5),
+ config: config
+ )
+
+ release.add_entry(
+ title: 'This is a new change',
+ commit: commit,
+ category: 'added',
+ author: author
+ )
+
+ gen = described_class.new('')
+
+ expect(gen.add(release)).to eq(<<~MARKDOWN)
+ ## 1.0.0 (2021-01-05)
+
+ ### added (1 change)
+
+ - [This is a new change](#{commit.to_reference(full: true)})
+ MARKDOWN
+ end
+
+ it 'generates the Markdown for a newer release' do
+ release = Gitlab::Changelog::Release.new(
+ version: '2.0.0',
+ date: Time.utc(2021, 1, 5),
+ config: config
+ )
+
+ release.add_entry(
+ title: 'This is a new change',
+ commit: commit,
+ category: 'added',
+ author: author
+ )
+
+ gen = described_class.new(<<~MARKDOWN)
+ This is a changelog file.
+
+ ## 1.0.0
+
+ This is the changelog for version 1.0.0.
+ MARKDOWN
+
+ expect(gen.add(release)).to eq(<<~MARKDOWN)
+ This is a changelog file.
+
+ ## 2.0.0 (2021-01-05)
+
+ ### added (1 change)
+
+ - [This is a new change](#{commit.to_reference(full: true)})
+
+ ## 1.0.0
+
+ This is the changelog for version 1.0.0.
+ MARKDOWN
+ end
+
+ it 'generates the Markdown for a patch release' do
+ release = Gitlab::Changelog::Release.new(
+ version: '1.1.0',
+ date: Time.utc(2021, 1, 5),
+ config: config
+ )
+
+ release.add_entry(
+ title: 'This is a new change',
+ commit: commit,
+ category: 'added',
+ author: author
+ )
+
+ gen = described_class.new(<<~MARKDOWN)
+ This is a changelog file.
+
+ ## 2.0.0
+
+ This is another release.
+
+ ## 1.0.0
+
+ This is the changelog for version 1.0.0.
+ MARKDOWN
+
+ expect(gen.add(release)).to eq(<<~MARKDOWN)
+ This is a changelog file.
+
+ ## 2.0.0
+
+ This is another release.
+
+ ## 1.1.0 (2021-01-05)
+
+ ### added (1 change)
+
+ - [This is a new change](#{commit.to_reference(full: true)})
+
+ ## 1.0.0
+
+ This is the changelog for version 1.0.0.
+ MARKDOWN
+ end
+
+ it 'generates the Markdown for an old release' do
+ release = Gitlab::Changelog::Release.new(
+ version: '0.5.0',
+ date: Time.utc(2021, 1, 5),
+ config: config
+ )
+
+ release.add_entry(
+ title: 'This is a new change',
+ commit: commit,
+ category: 'added',
+ author: author
+ )
+
+ gen = described_class.new(<<~MARKDOWN)
+ This is a changelog file.
+
+ ## 2.0.0
+
+ This is another release.
+
+ ## 1.0.0
+
+ This is the changelog for version 1.0.0.
+ MARKDOWN
+
+ expect(gen.add(release)).to eq(<<~MARKDOWN)
+ This is a changelog file.
+
+ ## 2.0.0
+
+ This is another release.
+
+ ## 1.0.0
+
+ This is the changelog for version 1.0.0.
+
+ ## 0.5.0 (2021-01-05)
+
+ ### added (1 change)
+
+ - [This is a new change](#{commit.to_reference(full: true)})
+ MARKDOWN
+ end
+ end
+end
diff --git a/spec/lib/gitlab/changelog/release_spec.rb b/spec/lib/gitlab/changelog/release_spec.rb
new file mode 100644
index 00000000000..50a23d23299
--- /dev/null
+++ b/spec/lib/gitlab/changelog/release_spec.rb
@@ -0,0 +1,107 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Changelog::Release do
+ describe '#to_markdown' do
+ let(:config) { Gitlab::Changelog::Config.new(build_stubbed(:project)) }
+ let(:commit) { build_stubbed(:commit) }
+ let(:author) { build_stubbed(:user) }
+ let(:mr) { build_stubbed(:merge_request) }
+ let(:release) do
+ described_class
+ .new(version: '1.0.0', date: Time.utc(2021, 1, 5), config: config)
+ end
+
+ context 'when there are no entries' do
+ it 'includes a notice about the lack of entries' do
+ expect(release.to_markdown).to eq(<<~OUT)
+ ## 1.0.0 (2021-01-05)
+
+ No changes.
+
+ OUT
+ end
+ end
+
+ context 'when all data is present' do
+ it 'includes all data' do
+ allow(config).to receive(:contributor?).with(author).and_return(true)
+
+ release.add_entry(
+ title: 'Entry title',
+ commit: commit,
+ category: 'fixed',
+ author: author,
+ merge_request: mr
+ )
+
+ expect(release.to_markdown).to eq(<<~OUT)
+ ## 1.0.0 (2021-01-05)
+
+ ### fixed (1 change)
+
+ - [Entry title](#{commit.to_reference(full: true)}) \
+ by #{author.to_reference(full: true)} \
+ ([merge request](#{mr.to_reference(full: true)}))
+
+ OUT
+ end
+ end
+
+ context 'when no merge request is present' do
+ it "doesn't include a merge request link" do
+ allow(config).to receive(:contributor?).with(author).and_return(true)
+
+ release.add_entry(
+ title: 'Entry title',
+ commit: commit,
+ category: 'fixed',
+ author: author
+ )
+
+ expect(release.to_markdown).to eq(<<~OUT)
+ ## 1.0.0 (2021-01-05)
+
+ ### fixed (1 change)
+
+ - [Entry title](#{commit.to_reference(full: true)}) \
+ by #{author.to_reference(full: true)}
+
+ OUT
+ end
+ end
+
+ context 'when the author is not a contributor' do
+ it "doesn't include the author" do
+ allow(config).to receive(:contributor?).with(author).and_return(false)
+
+ release.add_entry(
+ title: 'Entry title',
+ commit: commit,
+ category: 'fixed',
+ author: author
+ )
+
+ expect(release.to_markdown).to eq(<<~OUT)
+ ## 1.0.0 (2021-01-05)
+
+ ### fixed (1 change)
+
+ - [Entry title](#{commit.to_reference(full: true)})
+
+ OUT
+ end
+ end
+ end
+
+ describe '#header_start_position' do
+ it 'returns a regular expression for finding the start of a release section' do
+ config = Gitlab::Changelog::Config.new(build_stubbed(:project))
+ release = described_class
+ .new(version: '1.0.0', date: Time.utc(2021, 1, 5), config: config)
+
+ expect(release.header_start_pattern).to eq(/^##\s*1\.0\.0/)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/changelog/template/compiler_spec.rb b/spec/lib/gitlab/changelog/template/compiler_spec.rb
new file mode 100644
index 00000000000..8b09bc90529
--- /dev/null
+++ b/spec/lib/gitlab/changelog/template/compiler_spec.rb
@@ -0,0 +1,136 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Changelog::Template::Compiler do
+ def compile(template, data = {})
+ Gitlab::Changelog::Template::Compiler.new.compile(template).render(data)
+ end
+
+ describe '#compile' do
+ it 'compiles an empty template' do
+ expect(compile('')).to eq('')
+ end
+
+ it 'compiles a template with an undefined variable' do
+ expect(compile('{{number}}')).to eq('')
+ end
+
+ it 'compiles a template with a defined variable' do
+ expect(compile('{{number}}', 'number' => 42)).to eq('42')
+ end
+
+ it 'compiles a template with the special "it" variable' do
+ expect(compile('{{it}}', 'values' => 10)).to eq({ 'values' => 10 }.to_s)
+ end
+
+ it 'compiles a template containing an if statement' do
+ expect(compile('{% if foo %}yes{% end %}', 'foo' => true)).to eq('yes')
+ end
+
+ it 'compiles a template containing an if/else statement' do
+ expect(compile('{% if foo %}yes{% else %}no{% end %}', 'foo' => false))
+ .to eq('no')
+ end
+
+ it 'compiles a template that iterates over an Array' do
+ expect(compile('{% each numbers %}{{it}}{% end %}', 'numbers' => [1, 2, 3]))
+ .to eq('123')
+ end
+
+ it 'compiles a template that iterates over a Hash' do
+ output = compile(
+ '{% each pairs %}{{0}}={{1}}{% end %}',
+ 'pairs' => { 'key' => 'value' }
+ )
+
+ expect(output).to eq('key=value')
+ end
+
+ it 'compiles a template that iterates over a Hash of Arrays' do
+ output = compile(
+ '{% each values %}{{key}}{% end %}',
+ 'values' => [{ 'key' => 'value' }]
+ )
+
+ expect(output).to eq('value')
+ end
+
+ it 'compiles a template with a variable path' do
+ output = compile('{{foo.bar}}', 'foo' => { 'bar' => 10 })
+
+ expect(output).to eq('10')
+ end
+
+ it 'compiles a template with a variable path that uses an Array index' do
+ output = compile('{{foo.values.1}}', 'foo' => { 'values' => [10, 20] })
+
+ expect(output).to eq('20')
+ end
+
+ it 'compiles a template with a variable path that uses a Hash and a numeric index' do
+ output = compile('{{foo.1}}', 'foo' => { 'key' => 'value' })
+
+ expect(output).to eq('')
+ end
+
+ it 'compiles a template with a variable path that uses an Array and a String based index' do
+ output = compile('{{foo.numbers.bla}}', 'foo' => { 'numbers' => [10, 20] })
+
+ expect(output).to eq('')
+ end
+
+ it 'ignores ERB tags provided by the user' do
+ input = '<% exit %> <%= exit %> <%= foo -%>'
+
+ expect(compile(input)).to eq(input)
+ end
+
+ it 'removes newlines introduced by end statements on their own lines' do
+ output = compile(<<~TPL, 'foo' => true)
+ {% if foo %}
+ foo
+ {% end %}
+ TPL
+
+ expect(output).to eq("foo\n")
+ end
+
+ it 'supports escaping of trailing newlines' do
+ output = compile(<<~TPL)
+ foo \
+ bar\
+ baz
+ TPL
+
+ expect(output).to eq("foo barbaz\n")
+ end
+
+ # rubocop: disable Lint/InterpolationCheck
+ it 'ignores embedded Ruby expressions' do
+ input = '#{exit}'
+
+ expect(compile(input)).to eq(input)
+ end
+ # rubocop: enable Lint/InterpolationCheck
+
+ it 'ignores ERB tags inside variable tags' do
+ input = '{{<%= exit %>}}'
+
+ expect(compile(input)).to eq(input)
+ end
+
+ it 'ignores malicious code that tries to escape a variable' do
+ input = "{{') ::Kernel.exit # '}}"
+
+ expect(compile(input)).to eq(input)
+ end
+
+ it 'ignores malicious code that makes use of whitespace' do
+ input = "x<\\\n%::Kernel.system(\"id\")%>"
+
+ expect(Kernel).not_to receive(:system).with('id')
+ expect(compile(input)).to eq('x<%::Kernel.system("id")%>')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/badge/coverage/metadata_spec.rb b/spec/lib/gitlab/ci/badge/coverage/metadata_spec.rb
index 725ae03ad74..6d272f060ab 100644
--- a/spec/lib/gitlab/badge/coverage/metadata_spec.rb
+++ b/spec/lib/gitlab/ci/badge/coverage/metadata_spec.rb
@@ -1,9 +1,9 @@
# frozen_string_literal: true
require 'spec_helper'
-require 'lib/gitlab/badge/shared/metadata'
+require 'lib/gitlab/ci/badge/shared/metadata'
-RSpec.describe Gitlab::Badge::Coverage::Metadata do
+RSpec.describe Gitlab::Ci::Badge::Coverage::Metadata do
let(:badge) do
double(project: create(:project), ref: 'feature', job: 'test')
end
diff --git a/spec/lib/gitlab/badge/coverage/report_spec.rb b/spec/lib/gitlab/ci/badge/coverage/report_spec.rb
index 3b5ea3291e4..13696d815aa 100644
--- a/spec/lib/gitlab/badge/coverage/report_spec.rb
+++ b/spec/lib/gitlab/ci/badge/coverage/report_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Badge::Coverage::Report do
+RSpec.describe Gitlab::Ci::Badge::Coverage::Report do
let_it_be(:project) { create(:project) }
let_it_be(:success_pipeline) { create(:ci_pipeline, :success, project: project) }
let_it_be(:running_pipeline) { create(:ci_pipeline, :running, project: project) }
diff --git a/spec/lib/gitlab/badge/coverage/template_spec.rb b/spec/lib/gitlab/ci/badge/coverage/template_spec.rb
index ba5c1b2ce6e..f010d1bce50 100644
--- a/spec/lib/gitlab/badge/coverage/template_spec.rb
+++ b/spec/lib/gitlab/ci/badge/coverage/template_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Badge::Coverage::Template do
+RSpec.describe Gitlab::Ci::Badge::Coverage::Template do
let(:badge) { double(entity: 'coverage', status: 90.00, customization: {}) }
let(:template) { described_class.new(badge) }
diff --git a/spec/lib/gitlab/badge/pipeline/metadata_spec.rb b/spec/lib/gitlab/ci/badge/pipeline/metadata_spec.rb
index c8ed0c8ea29..2f677237fad 100644
--- a/spec/lib/gitlab/badge/pipeline/metadata_spec.rb
+++ b/spec/lib/gitlab/ci/badge/pipeline/metadata_spec.rb
@@ -1,9 +1,9 @@
# frozen_string_literal: true
require 'spec_helper'
-require 'lib/gitlab/badge/shared/metadata'
+require 'lib/gitlab/ci/badge/shared/metadata'
-RSpec.describe Gitlab::Badge::Pipeline::Metadata do
+RSpec.describe Gitlab::Ci::Badge::Pipeline::Metadata do
let(:badge) { double(project: create(:project), ref: 'feature') }
let(:metadata) { described_class.new(badge) }
diff --git a/spec/lib/gitlab/badge/pipeline/status_spec.rb b/spec/lib/gitlab/ci/badge/pipeline/status_spec.rb
index b5dabca0477..45d0d781090 100644
--- a/spec/lib/gitlab/badge/pipeline/status_spec.rb
+++ b/spec/lib/gitlab/ci/badge/pipeline/status_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Badge::Pipeline::Status do
+RSpec.describe Gitlab::Ci::Badge::Pipeline::Status do
let(:project) { create(:project, :repository) }
let(:sha) { project.commit.sha }
let(:branch) { 'master' }
diff --git a/spec/lib/gitlab/badge/pipeline/template_spec.rb b/spec/lib/gitlab/ci/badge/pipeline/template_spec.rb
index c78e95852f3..696bb62b4d6 100644
--- a/spec/lib/gitlab/badge/pipeline/template_spec.rb
+++ b/spec/lib/gitlab/ci/badge/pipeline/template_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Badge::Pipeline::Template do
+RSpec.describe Gitlab::Ci::Badge::Pipeline::Template do
let(:badge) { double(entity: 'pipeline', status: 'success', customization: {}) }
let(:template) { described_class.new(badge) }
diff --git a/spec/lib/gitlab/badge/shared/metadata.rb b/spec/lib/gitlab/ci/badge/shared/metadata.rb
index c99a65bb2f4..c99a65bb2f4 100644
--- a/spec/lib/gitlab/badge/shared/metadata.rb
+++ b/spec/lib/gitlab/ci/badge/shared/metadata.rb
diff --git a/spec/lib/gitlab/ci/build/credentials/registry/dependency_proxy_spec.rb b/spec/lib/gitlab/ci/build/credentials/registry/dependency_proxy_spec.rb
new file mode 100644
index 00000000000..f50c6e99e99
--- /dev/null
+++ b/spec/lib/gitlab/ci/build/credentials/registry/dependency_proxy_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Build::Credentials::Registry::DependencyProxy do
+ let(:build) { create(:ci_build, name: 'spinach', stage: 'test', stage_idx: 0) }
+ let(:gitlab_url) { 'gitlab.example.com:443' }
+
+ subject { described_class.new(build) }
+
+ before do
+ stub_config_setting(host: 'gitlab.example.com', port: 443)
+ end
+
+ it 'contains valid dependency proxy credentials' do
+ expect(subject).to be_kind_of(described_class)
+
+ expect(subject.username).to eq 'gitlab-ci-token'
+ expect(subject.password).to eq build.token
+ expect(subject.url).to eq gitlab_url
+ expect(subject.type).to eq 'registry'
+ end
+
+ describe '.valid?' do
+ subject { described_class.new(build).valid? }
+
+ context 'when dependency proxy is enabled' do
+ before do
+ stub_config(dependency_proxy: { enabled: true })
+ end
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when dependency proxy is disabled' do
+ before do
+ stub_config(dependency_proxy: { enabled: false })
+ end
+
+ it { is_expected.to be_falsey }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/build/credentials/registry_spec.rb b/spec/lib/gitlab/ci/build/credentials/registry/gitlab_registry_spec.rb
index c0a76973f60..43913e91085 100644
--- a/spec/lib/gitlab/ci/build/credentials/registry_spec.rb
+++ b/spec/lib/gitlab/ci/build/credentials/registry/gitlab_registry_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::Build::Credentials::Registry do
+RSpec.describe Gitlab::Ci::Build::Credentials::Registry::GitlabRegistry do
let(:build) { create(:ci_build, name: 'spinach', stage: 'test', stage_idx: 0) }
let(:registry_url) { 'registry.example.com:5005' }
diff --git a/spec/lib/gitlab/ci/build/rules_spec.rb b/spec/lib/gitlab/ci/build/rules_spec.rb
index a1af5b75f87..0b50def05d4 100644
--- a/spec/lib/gitlab/ci/build/rules_spec.rb
+++ b/spec/lib/gitlab/ci/build/rules_spec.rb
@@ -201,40 +201,13 @@ RSpec.describe Gitlab::Ci::Build::Rules do
end
describe '#build_attributes' do
- let(:seed_attributes) { {} }
-
subject(:build_attributes) do
- result.build_attributes(seed_attributes)
+ result.build_attributes
end
it 'compacts nil values' do
is_expected.to eq(options: {}, when: 'on_success')
end
-
- context 'when there are variables in rules' do
- let(:variables) { { VAR1: 'new var 1', VAR3: 'var 3' } }
-
- context 'when there are seed variables' do
- let(:seed_attributes) do
- { yaml_variables: [{ key: 'VAR1', value: 'var 1', public: true },
- { key: 'VAR2', value: 'var 2', public: true }] }
- end
-
- it 'returns yaml_variables with override' do
- is_expected.to include(
- yaml_variables: [{ key: 'VAR1', value: 'new var 1', public: true },
- { key: 'VAR2', value: 'var 2', public: true },
- { key: 'VAR3', value: 'var 3', public: true }]
- )
- end
- end
-
- context 'when there is not seed variables' do
- it 'does not return yaml_variables' do
- is_expected.not_to have_key(:yaml_variables)
- end
- end
- end
end
describe '#pass?' do
diff --git a/spec/lib/gitlab/ci/charts_spec.rb b/spec/lib/gitlab/ci/charts_spec.rb
index cfc2019a89b..46d7d4a58f0 100644
--- a/spec/lib/gitlab/ci/charts_spec.rb
+++ b/spec/lib/gitlab/ci/charts_spec.rb
@@ -9,6 +9,10 @@ RSpec.describe Gitlab::Ci::Charts do
subject { chart.to }
+ before do
+ create(:ci_empty_pipeline, project: project, duration: 120)
+ end
+
it 'goes until the end of the current month (including the whole last day of the month)' do
is_expected.to eq(Date.today.end_of_month.end_of_day)
end
@@ -20,6 +24,10 @@ RSpec.describe Gitlab::Ci::Charts do
it 'uses %B %Y as labels format' do
expect(chart.labels).to include(chart.from.strftime('%B %Y'))
end
+
+ it 'returns count of pipelines run each day in the current year' do
+ expect(chart.total.sum).to eq(1)
+ end
end
context 'monthchart' do
@@ -28,6 +36,10 @@ RSpec.describe Gitlab::Ci::Charts do
subject { chart.to }
+ before do
+ create(:ci_empty_pipeline, project: project, duration: 120)
+ end
+
it 'includes the whole current day' do
is_expected.to eq(Date.today.end_of_day)
end
@@ -39,6 +51,10 @@ RSpec.describe Gitlab::Ci::Charts do
it 'uses %d %B as labels format' do
expect(chart.labels).to include(chart.from.strftime('%d %B'))
end
+
+ it 'returns count of pipelines run each day in the current month' do
+ expect(chart.total.sum).to eq(1)
+ end
end
context 'weekchart' do
@@ -47,6 +63,10 @@ RSpec.describe Gitlab::Ci::Charts do
subject { chart.to }
+ before do
+ create(:ci_empty_pipeline, project: project, duration: 120)
+ end
+
it 'includes the whole current day' do
is_expected.to eq(Date.today.end_of_day)
end
@@ -58,6 +78,68 @@ RSpec.describe Gitlab::Ci::Charts do
it 'uses %d %B as labels format' do
expect(chart.labels).to include(chart.from.strftime('%d %B'))
end
+
+ it 'returns count of pipelines run each day in the current week' do
+ expect(chart.total.sum).to eq(1)
+ end
+ end
+
+ context 'weekchart_utc' do
+ today = Date.today
+ end_of_today = Time.use_zone(Time.find_zone('UTC')) { today.end_of_day }
+
+ let(:project) { create(:project) }
+ let(:chart) do
+ allow(Date).to receive(:today).and_return(today)
+ allow(today).to receive(:end_of_day).and_return(end_of_today)
+ Gitlab::Ci::Charts::WeekChart.new(project)
+ end
+
+ subject { chart.total }
+
+ before do
+ create(:ci_empty_pipeline, project: project, duration: 120)
+ end
+
+ it 'uses a utc time zone for range times' do
+ expect(chart.to.zone).to eq(end_of_today.zone)
+ expect(chart.from.zone).to eq(end_of_today.zone)
+ end
+
+ it 'returns count of pipelines run each day in the current week' do
+ expect(chart.total.sum).to eq(1)
+ end
+ end
+
+ context 'weekchart_non_utc' do
+ today = Date.today
+ end_of_today = Time.use_zone(Time.find_zone('Asia/Dubai')) { today.end_of_day }
+
+ let(:project) { create(:project) }
+ let(:chart) do
+ allow(Date).to receive(:today).and_return(today)
+ allow(today).to receive(:end_of_day).and_return(end_of_today)
+ Gitlab::Ci::Charts::WeekChart.new(project)
+ end
+
+ subject { chart.total }
+
+ before do
+ # The DB uses UTC always, so our use of a Time Zone in the application
+ # can cause the creation date of the pipeline to go unmatched depending
+ # on the offset. We can work around this by requesting the pipeline be
+ # created a with the `created_at` field set to a day ago in the same week.
+ create(:ci_empty_pipeline, project: project, duration: 120, created_at: today - 1.day)
+ end
+
+ it 'uses a non-utc time zone for range times' do
+ expect(chart.to.zone).to eq(end_of_today.zone)
+ expect(chart.from.zone).to eq(end_of_today.zone)
+ end
+
+ it 'returns count of pipelines run each day in the current week' do
+ expect(chart.total.sum).to eq(1)
+ end
end
context 'pipeline_times' do
diff --git a/spec/lib/gitlab/ci/config/entry/cache_spec.rb b/spec/lib/gitlab/ci/config/entry/cache_spec.rb
index 80427eaa6ee..247f4b63910 100644
--- a/spec/lib/gitlab/ci/config/entry/cache_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/cache_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe Gitlab::Ci::Config::Entry::Cache do
+ using RSpec::Parameterized::TableSyntax
+
subject(:entry) { described_class.new(config) }
describe 'validations' do
@@ -56,8 +58,6 @@ RSpec.describe Gitlab::Ci::Config::Entry::Cache do
end
context 'with `policy`' do
- using RSpec::Parameterized::TableSyntax
-
where(:policy, :result) do
'pull-push' | 'pull-push'
'push' | 'push'
@@ -77,8 +77,6 @@ RSpec.describe Gitlab::Ci::Config::Entry::Cache do
end
context 'with `when`' do
- using RSpec::Parameterized::TableSyntax
-
where(:when_config, :result) do
'on_success' | 'on_success'
'on_failure' | 'on_failure'
@@ -109,8 +107,6 @@ RSpec.describe Gitlab::Ci::Config::Entry::Cache do
end
context 'with `policy`' do
- using RSpec::Parameterized::TableSyntax
-
where(:policy, :valid) do
'pull-push' | true
'push' | true
@@ -126,8 +122,6 @@ RSpec.describe Gitlab::Ci::Config::Entry::Cache do
end
context 'with `when`' do
- using RSpec::Parameterized::TableSyntax
-
where(:when_config, :valid) do
'on_success' | true
'on_failure' | true
diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb
index 7834a1a94f2..a3b5f32b9f9 100644
--- a/spec/lib/gitlab/ci/config/entry/job_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb
@@ -763,16 +763,6 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job do
it 'returns allow_failure_criteria' do
expect(entry.value[:allow_failure_criteria]).to match(exit_codes: [42])
end
-
- context 'with ci_allow_failure_with_exit_codes disabled' do
- before do
- stub_feature_flags(ci_allow_failure_with_exit_codes: false)
- end
-
- it 'does not return allow_failure_criteria' do
- expect(entry.value.key?(:allow_failure_criteria)).to be_falsey
- end
- end
end
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/processable_spec.rb b/spec/lib/gitlab/ci/config/entry/processable_spec.rb
index aadf94365c6..04e80450263 100644
--- a/spec/lib/gitlab/ci/config/entry/processable_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/processable_spec.rb
@@ -73,6 +73,15 @@ RSpec.describe Gitlab::Ci::Config::Entry::Processable do
end
end
+ context 'when resource_group key is not a string' do
+ let(:config) { { resource_group: 123 } }
+
+ it 'returns error about wrong value type' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include "job resource group should be a string"
+ end
+ end
+
context 'when it uses both "when:" and "rules:"' do
let(:config) do
{
@@ -340,6 +349,26 @@ RSpec.describe Gitlab::Ci::Config::Entry::Processable do
end
end
+ context 'with resource group' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:resource_group, :result) do
+ 'iOS' | 'iOS'
+ 'review/$CI_COMMIT_REF_NAME' | 'review/$CI_COMMIT_REF_NAME'
+ nil | nil
+ end
+
+ with_them do
+ let(:config) { { script: 'ls', resource_group: resource_group }.compact }
+
+ it do
+ entry.compose!(deps)
+
+ expect(entry.resource_group).to eq(result)
+ end
+ end
+ end
+
context 'with inheritance' do
context 'of variables' do
let(:config) do
diff --git a/spec/lib/gitlab/ci/config/external/mapper_spec.rb b/spec/lib/gitlab/ci/config/external/mapper_spec.rb
index 4fdaaca8316..99f546ceb37 100644
--- a/spec/lib/gitlab/ci/config/external/mapper_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/mapper_spec.rb
@@ -323,20 +323,6 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper do
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/cron_parser_spec.rb b/spec/lib/gitlab/ci/cron_parser_spec.rb
index dd27b4045c9..15293429354 100644
--- a/spec/lib/gitlab/ci/cron_parser_spec.rb
+++ b/spec/lib/gitlab/ci/cron_parser_spec.rb
@@ -63,6 +63,17 @@ RSpec.describe Gitlab::Ci::CronParser do
end
end
+ context 'when range and slash used' do
+ let(:cron) { '3-59/10 * * * *' }
+ let(:cron_timezone) { 'UTC' }
+
+ it_behaves_like returns_time_for_epoch
+
+ it 'returns specific time' do
+ expect(subject.min).to be_in([3, 13, 23, 33, 43, 53])
+ end
+ end
+
context 'when cron_timezone is TZInfo format' do
before do
allow(Time).to receive(:zone)
diff --git a/spec/lib/gitlab/ci/parsers/instrumentation_spec.rb b/spec/lib/gitlab/ci/parsers/instrumentation_spec.rb
new file mode 100644
index 00000000000..30bcce21be2
--- /dev/null
+++ b/spec/lib/gitlab/ci/parsers/instrumentation_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Parsers::Instrumentation do
+ describe '#parse!' do
+ let(:parser_class) do
+ Class.new do
+ prepend Gitlab::Ci::Parsers::Instrumentation
+
+ def parse!(arg1, arg2)
+ "parse #{arg1} #{arg2}"
+ end
+ end
+ end
+
+ it 'sets metrics for duration of parsing' do
+ result = parser_class.new.parse!('hello', 'world')
+
+ expect(result).to eq('parse hello world')
+
+ metrics = Gitlab::Metrics.registry.get(:ci_report_parser_duration_seconds).get({ parser: parser_class.name })
+
+ expect(metrics.keys).to match_array(described_class::BUCKETS)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/parsers_spec.rb b/spec/lib/gitlab/ci/parsers_spec.rb
index b932cd81272..c9891c06507 100644
--- a/spec/lib/gitlab/ci/parsers_spec.rb
+++ b/spec/lib/gitlab/ci/parsers_spec.rb
@@ -54,4 +54,12 @@ RSpec.describe Gitlab::Ci::Parsers do
end
end
end
+
+ describe '.instrument!' do
+ it 'prepends the Instrumentation module into each parser' do
+ expect(described_class.parsers.values).to all( receive(:prepend).with(Gitlab::Ci::Parsers::Instrumentation) )
+
+ described_class.instrument!
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/cancel_pending_pipelines_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/cancel_pending_pipelines_spec.rb
index 3eaecb11ae0..1d17244e519 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/cancel_pending_pipelines_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/cancel_pending_pipelines_spec.rb
@@ -58,20 +58,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::CancelPendingPipelines do
expect(build_statuses(child_pipeline)).to contain_exactly('canceled')
end
-
- context 'when FF ci_auto_cancel_all_pipelines is disabled' do
- before do
- stub_feature_flags(ci_auto_cancel_all_pipelines: false)
- end
-
- it 'does not cancel interruptible builds of child pipeline' do
- expect(build_statuses(child_pipeline)).to contain_exactly('running')
-
- perform
-
- expect(build_statuses(child_pipeline)).to contain_exactly('running')
- end
- end
end
context 'when the child pipeline has not an interruptible job' do
diff --git a/spec/lib/gitlab/ci/reports/codequality_mr_diff_spec.rb b/spec/lib/gitlab/ci/reports/codequality_mr_diff_spec.rb
new file mode 100644
index 00000000000..8b177fa7fc1
--- /dev/null
+++ b/spec/lib/gitlab/ci/reports/codequality_mr_diff_spec.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Reports::CodequalityMrDiff do
+ let(:codequality_report) { Gitlab::Ci::Reports::CodequalityReports.new }
+ let(:degradation_1) { build(:codequality_degradation_1) }
+ let(:degradation_2) { build(:codequality_degradation_2) }
+ let(:degradation_3) { build(:codequality_degradation_3) }
+
+ describe '#initialize!' do
+ subject(:report) { described_class.new(codequality_report) }
+
+ context 'when quality has degradations' do
+ context 'with several degradations on the same line' do
+ before do
+ codequality_report.add_degradation(degradation_1)
+ codequality_report.add_degradation(degradation_2)
+ end
+
+ it 'generates quality report for mr diff' do
+ expect(report.files).to match(
+ "file_a.rb" => [
+ { line: 10, description: "Avoid parameter lists longer than 5 parameters. [12/5]", severity: "major" },
+ { line: 10, description: "Method `new_array` has 12 arguments (exceeds 4 allowed). Consider refactoring.", severity: "major" }
+ ]
+ )
+ end
+ end
+
+ context 'with several degradations on several files' do
+ before do
+ codequality_report.add_degradation(degradation_1)
+ codequality_report.add_degradation(degradation_2)
+ codequality_report.add_degradation(degradation_3)
+ end
+
+ it 'returns quality report for mr diff' do
+ expect(report.files).to match(
+ "file_a.rb" => [
+ { line: 10, description: "Avoid parameter lists longer than 5 parameters. [12/5]", severity: "major" },
+ { line: 10, description: "Method `new_array` has 12 arguments (exceeds 4 allowed). Consider refactoring.", severity: "major" }
+ ],
+ "file_b.rb" => [
+ { line: 10, description: "Avoid parameter lists longer than 5 parameters. [12/5]", severity: "minor" }
+ ]
+ )
+ end
+ end
+ end
+
+ context 'when quality has no degradation' do
+ it 'returns an empty hash' do
+ expect(report.files).to match({})
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/reports/codequality_reports_comparer_spec.rb b/spec/lib/gitlab/ci/reports/codequality_reports_comparer_spec.rb
index 7053d54381b..90188b56f5a 100644
--- a/spec/lib/gitlab/ci/reports/codequality_reports_comparer_spec.rb
+++ b/spec/lib/gitlab/ci/reports/codequality_reports_comparer_spec.rb
@@ -6,62 +6,8 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
let(:comparer) { described_class.new(base_report, head_report) }
let(:base_report) { Gitlab::Ci::Reports::CodequalityReports.new }
let(:head_report) { Gitlab::Ci::Reports::CodequalityReports.new }
- let(:degradation_1) do
- {
- "categories": [
- "Complexity"
- ],
- "check_name": "argument_count",
- "content": {
- "body": ""
- },
- "description": "Method `new_array` has 12 arguments (exceeds 4 allowed). Consider refactoring.",
- "fingerprint": "15cdb5c53afd42bc22f8ca366a08d547",
- "location": {
- "path": "foo.rb",
- "lines": {
- "begin": 10,
- "end": 10
- }
- },
- "other_locations": [],
- "remediation_points": 900000,
- "severity": "major",
- "type": "issue",
- "engine_name": "structure"
- }.with_indifferent_access
- end
-
- let(:degradation_2) do
- {
- "type": "Issue",
- "check_name": "Rubocop/Metrics/ParameterLists",
- "description": "Avoid parameter lists longer than 5 parameters. [12/5]",
- "categories": [
- "Complexity"
- ],
- "remediation_points": 550000,
- "location": {
- "path": "foo.rb",
- "positions": {
- "begin": {
- "column": 14,
- "line": 10
- },
- "end": {
- "column": 39,
- "line": 10
- }
- }
- },
- "content": {
- "body": "This cop checks for methods with too many parameters.\nThe maximum number of parameters is configurable.\nKeyword arguments can optionally be excluded from the total count."
- },
- "engine_name": "rubocop",
- "fingerprint": "ab5f8b935886b942d621399f5a2ca16e",
- "severity": "minor"
- }.with_indifferent_access
- end
+ let(:degradation_1) { build(:codequality_degradation_1) }
+ let(:degradation_2) { build(:codequality_degradation_2) }
describe '#status' do
subject(:report_status) { comparer.status }
diff --git a/spec/lib/gitlab/ci/reports/codequality_reports_spec.rb b/spec/lib/gitlab/ci/reports/codequality_reports_spec.rb
index 44e67259369..ae9b2f2c62b 100644
--- a/spec/lib/gitlab/ci/reports/codequality_reports_spec.rb
+++ b/spec/lib/gitlab/ci/reports/codequality_reports_spec.rb
@@ -4,62 +4,8 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Reports::CodequalityReports do
let(:codequality_report) { described_class.new }
- let(:degradation_1) do
- {
- "categories": [
- "Complexity"
- ],
- "check_name": "argument_count",
- "content": {
- "body": ""
- },
- "description": "Method `new_array` has 12 arguments (exceeds 4 allowed). Consider refactoring.",
- "fingerprint": "15cdb5c53afd42bc22f8ca366a08d547",
- "location": {
- "path": "foo.rb",
- "lines": {
- "begin": 10,
- "end": 10
- }
- },
- "other_locations": [],
- "remediation_points": 900000,
- "severity": "major",
- "type": "issue",
- "engine_name": "structure"
- }.with_indifferent_access
- end
-
- let(:degradation_2) do
- {
- "type": "Issue",
- "check_name": "Rubocop/Metrics/ParameterLists",
- "description": "Avoid parameter lists longer than 5 parameters. [12/5]",
- "categories": [
- "Complexity"
- ],
- "remediation_points": 550000,
- "location": {
- "path": "foo.rb",
- "positions": {
- "begin": {
- "column": 14,
- "line": 10
- },
- "end": {
- "column": 39,
- "line": 10
- }
- }
- },
- "content": {
- "body": "This cop checks for methods with too many parameters.\nThe maximum number of parameters is configurable.\nKeyword arguments can optionally be excluded from the total count."
- },
- "engine_name": "rubocop",
- "fingerprint": "ab5f8b935886b942d621399f5a2ca16e",
- "severity": "minor"
- }.with_indifferent_access
- end
+ let(:degradation_1) { build(:codequality_degradation_1) }
+ let(:degradation_2) { build(:codequality_degradation_2) }
it { expect(codequality_report.degradations).to eq({}) }
diff --git a/spec/lib/gitlab/ci/trace/chunked_io_spec.rb b/spec/lib/gitlab/ci/trace/chunked_io_spec.rb
index a2903391c6f..f09e03b4d55 100644
--- a/spec/lib/gitlab/ci/trace/chunked_io_spec.rb
+++ b/spec/lib/gitlab/ci/trace/chunked_io_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe Gitlab::Ci::Trace::ChunkedIO, :clean_gitlab_redis_cache do
let(:chunked_io) { described_class.new(build) }
before do
- stub_feature_flags(ci_enable_live_trace: true)
+ stub_feature_flags(ci_enable_live_trace: true, gitlab_ci_trace_read_consistency: true)
end
describe "#initialize" do
diff --git a/spec/lib/gitlab/ci/variables/helpers_spec.rb b/spec/lib/gitlab/ci/variables/helpers_spec.rb
new file mode 100644
index 00000000000..b45abf8c0e1
--- /dev/null
+++ b/spec/lib/gitlab/ci/variables/helpers_spec.rb
@@ -0,0 +1,103 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+RSpec.describe Gitlab::Ci::Variables::Helpers do
+ describe '.merge_variables' do
+ let(:current_variables) do
+ [{ key: 'key1', value: 'value1' },
+ { key: 'key2', value: 'value2' }]
+ end
+
+ let(:new_variables) do
+ [{ key: 'key2', value: 'value22' },
+ { key: 'key3', value: 'value3' }]
+ end
+
+ let(:result) do
+ [{ key: 'key1', value: 'value1', public: true },
+ { key: 'key2', value: 'value22', public: true },
+ { key: 'key3', value: 'value3', public: true }]
+ end
+
+ subject { described_class.merge_variables(current_variables, new_variables) }
+
+ it { is_expected.to eq(result) }
+
+ context 'when new variables is a hash' do
+ let(:new_variables) do
+ { 'key2' => 'value22', 'key3' => 'value3' }
+ end
+
+ it { is_expected.to eq(result) }
+ end
+
+ context 'when new variables is a hash with symbol keys' do
+ let(:new_variables) do
+ { key2: 'value22', key3: 'value3' }
+ end
+
+ it { is_expected.to eq(result) }
+ end
+
+ context 'when new variables is nil' do
+ let(:new_variables) {}
+ let(:result) do
+ [{ key: 'key1', value: 'value1', public: true },
+ { key: 'key2', value: 'value2', public: true }]
+ end
+
+ it { is_expected.to eq(result) }
+ end
+ end
+
+ describe '.transform_to_yaml_variables' do
+ let(:variables) do
+ { 'key1' => 'value1', 'key2' => 'value2' }
+ end
+
+ let(:result) do
+ [{ key: 'key1', value: 'value1', public: true },
+ { key: 'key2', value: 'value2', public: true }]
+ end
+
+ subject { described_class.transform_to_yaml_variables(variables) }
+
+ it { is_expected.to eq(result) }
+
+ context 'when variables is nil' do
+ let(:variables) {}
+
+ it { is_expected.to eq([]) }
+ end
+ end
+
+ describe '.transform_from_yaml_variables' do
+ let(:variables) do
+ [{ key: 'key1', value: 'value1', public: true },
+ { key: 'key2', value: 'value2', public: true }]
+ end
+
+ let(:result) do
+ { 'key1' => 'value1', 'key2' => 'value2' }
+ end
+
+ subject { described_class.transform_from_yaml_variables(variables) }
+
+ it { is_expected.to eq(result) }
+
+ context 'when variables is nil' do
+ let(:variables) {}
+
+ it { is_expected.to eq({}) }
+ end
+
+ context 'when variables is a hash' do
+ let(:variables) do
+ { key1: 'value1', 'key2' => 'value2' }
+ end
+
+ it { is_expected.to eq(result) }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/cleanup/orphan_job_artifact_files_spec.rb b/spec/lib/gitlab/cleanup/orphan_job_artifact_files_spec.rb
index 8a7425a4156..b5adb603dab 100644
--- a/spec/lib/gitlab/cleanup/orphan_job_artifact_files_spec.rb
+++ b/spec/lib/gitlab/cleanup/orphan_job_artifact_files_spec.rb
@@ -42,7 +42,8 @@ RSpec.describe Gitlab::Cleanup::OrphanJobArtifactFiles do
end
it 'stops when limit is reached' do
- cleanup = described_class.new(limit: 1)
+ stub_env('LIMIT', 1)
+ cleanup = described_class.new
mock_artifacts_found(cleanup, 'tmp/foo/bar/1', 'tmp/foo/bar/2')
diff --git a/spec/lib/gitlab/cluster/lifecycle_events_spec.rb b/spec/lib/gitlab/cluster/lifecycle_events_spec.rb
new file mode 100644
index 00000000000..4ed68d54680
--- /dev/null
+++ b/spec/lib/gitlab/cluster/lifecycle_events_spec.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require 'rspec-parameterized'
+
+RSpec.describe Gitlab::Cluster::LifecycleEvents do
+ # we create a new instance to ensure that we do not touch existing hooks
+ let(:replica) { Class.new(described_class) }
+
+ context 'hooks execution' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:method, :hook_names) do
+ :do_worker_start | %i[worker_start_hooks]
+ :do_before_fork | %i[before_fork_hooks]
+ :do_before_graceful_shutdown | %i[master_blackout_period master_graceful_shutdown]
+ :do_before_master_restart | %i[master_restart_hooks]
+ end
+
+ before do
+ # disable blackout period to speed-up tests
+ stub_config(shutdown: { blackout_seconds: 0 })
+ end
+
+ with_them do
+ subject { replica.public_send(method) }
+
+ it 'executes all hooks' do
+ hook_names.each do |hook_name|
+ hook = double
+ replica.instance_variable_set(:"@#{hook_name}", [hook])
+
+ # ensure that proper hooks are called
+ expect(hook).to receive(:call)
+ expect(replica).to receive(:call).with(hook_name, anything).and_call_original
+ end
+
+ subject
+ end
+ end
+ end
+
+ describe '#call' do
+ let(:name) { :my_hooks }
+
+ subject { replica.send(:call, name, hooks) }
+
+ context 'when many hooks raise exception' do
+ let(:hooks) do
+ [
+ -> { raise 'Exception A' },
+ -> { raise 'Exception B' }
+ ]
+ end
+
+ context 'USE_FATAL_LIFECYCLE_EVENTS is set to default' do
+ it 'only first hook is executed and is fatal' do
+ expect(hooks[0]).to receive(:call).and_call_original
+ expect(hooks[1]).not_to receive(:call)
+
+ expect(Gitlab::ErrorTracking).to receive(:track_exception).and_call_original
+ expect(replica).to receive(:warn).with('ERROR: The hook my_hooks failed with exception (RuntimeError) "Exception A".')
+
+ expect { subject }.to raise_error(described_class::FatalError, 'Exception A')
+ end
+ end
+
+ context 'when USE_FATAL_LIFECYCLE_EVENTS is disabled' do
+ before do
+ stub_const('Gitlab::Cluster::LifecycleEvents::USE_FATAL_LIFECYCLE_EVENTS', false)
+ end
+
+ it 'many hooks are executed and all exceptions are logged' do
+ expect(hooks[0]).to receive(:call).and_call_original
+ expect(hooks[1]).to receive(:call).and_call_original
+
+ expect(Gitlab::ErrorTracking).to receive(:track_exception).twice.and_call_original
+ expect(replica).to receive(:warn).twice.and_call_original
+
+ expect { subject }.not_to raise_error
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/composer/cache_spec.rb b/spec/lib/gitlab/composer/cache_spec.rb
new file mode 100644
index 00000000000..00318ac14f9
--- /dev/null
+++ b/spec/lib/gitlab/composer/cache_spec.rb
@@ -0,0 +1,133 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Composer::Cache 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(:branch) { project.repository.find_branch('master') }
+ let(:sha_regex) { /^[A-Fa-f0-9]{64}$/ }
+
+ shared_examples 'Composer create cache page' do
+ let(:expected_json) { ::Gitlab::Composer::VersionIndex.new(packages).to_json }
+
+ before do
+ stub_composer_cache_object_storage
+ end
+
+ it 'creates the cached page' do
+ expect { subject }.to change { Packages::Composer::CacheFile.count }.by(1)
+ cache_file = Packages::Composer::CacheFile.last
+ expect(cache_file.file_sha256).to eq package.reload.composer_metadatum.version_cache_sha
+ expect(cache_file.file.read).to eq expected_json
+ end
+ end
+
+ shared_examples 'Composer marks cache page for deletion' do
+ it 'marks the page for deletion' do
+ cache_file = Packages::Composer::CacheFile.last
+
+ freeze_time do
+ expect { subject }.to change { cache_file.reload.delete_at}.from(nil).to(1.day.from_now)
+ end
+ end
+ end
+
+ describe '#execute' do
+ subject { described_class.new(project: project, name: package_name).execute }
+
+ context 'creating packages' do
+ context 'with a pre-existing package' do
+ let(:package) { create(:composer_package, :with_metadatum, project: project, name: package_name, version: '1.0.0', json: json) }
+ let(:package2) { create(:composer_package, :with_metadatum, project: project, name: package_name, version: '2.0.0', json: json) }
+ let(:packages) { [package, package2] }
+
+ before do
+ package
+ described_class.new(project: project, name: package_name).execute
+ package.reload
+ package2
+ end
+
+ it 'updates the sha and creates the cache page' do
+ expect { subject }.to change { package2.reload.composer_metadatum.version_cache_sha }.from(nil).to(sha_regex)
+ .and change { package.reload.composer_metadatum.version_cache_sha }.to(sha_regex)
+ end
+
+ it_behaves_like 'Composer create cache page'
+ it_behaves_like 'Composer marks cache page for deletion'
+ end
+
+ context 'first package' do
+ let!(:package) { create(:composer_package, :with_metadatum, project: project, name: package_name, version: '1.0.0', json: json) }
+ let(:packages) { [package] }
+
+ it 'updates the sha and creates the cache page' do
+ expect { subject }.to change { package.reload.composer_metadatum.version_cache_sha }.from(nil).to(sha_regex)
+ end
+
+ it_behaves_like 'Composer create cache page'
+ end
+ end
+
+ context 'updating packages' do
+ let(:package) { create(:composer_package, :with_metadatum, project: project, name: package_name, version: '1.0.0', json: json) }
+ let(:package2) { create(:composer_package, :with_metadatum, project: project, name: package_name, version: '2.0.0', json: json) }
+ let(:packages) { [package, package2] }
+
+ before do
+ packages
+
+ described_class.new(project: project, name: package_name).execute
+
+ package.update!(version: '1.2.0')
+ package.reload
+ end
+
+ it_behaves_like 'Composer create cache page'
+ it_behaves_like 'Composer marks cache page for deletion'
+ end
+
+ context 'deleting packages' do
+ context 'when it is not the last package' do
+ let(:package) { create(:composer_package, :with_metadatum, project: project, name: package_name, version: '1.0.0', json: json) }
+ let(:package2) { create(:composer_package, :with_metadatum, project: project, name: package_name, version: '2.0.0', json: json) }
+ let(:packages) { [package] }
+
+ before do
+ package
+ package2
+
+ described_class.new(project: project, name: package_name).execute
+
+ package2.destroy!
+ end
+
+ it_behaves_like 'Composer create cache page'
+ it_behaves_like 'Composer marks cache page for deletion'
+ end
+
+ context 'when it is the last package' do
+ let!(:package) { create(:composer_package, :with_metadatum, project: project, name: package_name, version: '1.0.0', json: json) }
+ let!(:last_sha) do
+ described_class.new(project: project, name: package_name).execute
+ package.reload.composer_metadatum.version_cache_sha
+ end
+
+ before do
+ package.destroy!
+ end
+
+ subject { described_class.new(project: project, name: package_name, last_page_sha: last_sha).execute }
+
+ it_behaves_like 'Composer marks cache page for deletion'
+
+ it 'does not create a new page' do
+ expect { subject }.not_to change { Packages::Composer::CacheFile.count }
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/composer/version_index_spec.rb b/spec/lib/gitlab/composer/version_index_spec.rb
index 4c4742d9f59..7b0ed703f42 100644
--- a/spec/lib/gitlab/composer/version_index_spec.rb
+++ b/spec/lib/gitlab/composer/version_index_spec.rb
@@ -15,7 +15,9 @@ RSpec.describe Gitlab::Composer::VersionIndex do
let(:packages) { [package1, package2] }
describe '#as_json' do
- subject(:index) { described_class.new(packages).as_json }
+ subject(:package_index) { index['packages'][package_name] }
+
+ let(:index) { described_class.new(packages).as_json }
def expected_json(package)
{
@@ -32,10 +34,16 @@ RSpec.describe Gitlab::Composer::VersionIndex do
end
it 'returns the packages json' do
- packages = index['packages'][package_name]
+ expect(package_index['1.0.0']).to eq(expected_json(package1))
+ expect(package_index['2.0.0']).to eq(expected_json(package2))
+ end
+
+ context 'with an unordered list of packages' do
+ let(:packages) { [package2, package1] }
- expect(packages['1.0.0']).to eq(expected_json(package1))
- expect(packages['2.0.0']).to eq(expected_json(package2))
+ it 'returns the packages sorted by version' do
+ expect(package_index.keys).to eq ['1.0.0', '2.0.0']
+ end
end
end
diff --git a/spec/lib/gitlab/conan_token_spec.rb b/spec/lib/gitlab/conan_token_spec.rb
index be1d3e757f5..00683cf6e47 100644
--- a/spec/lib/gitlab/conan_token_spec.rb
+++ b/spec/lib/gitlab/conan_token_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe Gitlab::ConanToken do
let(:jwt_secret) do
OpenSSL::HMAC.hexdigest(
- OpenSSL::Digest::SHA256.new,
+ OpenSSL::Digest.new('SHA256'),
base_secret,
described_class::HMAC_KEY
)
diff --git a/spec/lib/gitlab/crypto_helper_spec.rb b/spec/lib/gitlab/crypto_helper_spec.rb
index c07089d8ef0..024564ea213 100644
--- a/spec/lib/gitlab/crypto_helper_spec.rb
+++ b/spec/lib/gitlab/crypto_helper_spec.rb
@@ -19,21 +19,85 @@ RSpec.describe Gitlab::CryptoHelper do
expect(encrypted).to match %r{\A[A-Za-z0-9+/=]+\z}
expect(encrypted).not_to include "\n"
end
+
+ it 'does not save hashed token with iv value in database' do
+ expect { described_class.aes256_gcm_encrypt('some-value') }.not_to change { TokenWithIv.count }
+ end
+
+ it 'encrypts using static iv' do
+ expect(Encryptor).to receive(:encrypt).with(described_class::AES256_GCM_OPTIONS.merge(value: 'some-value', iv: described_class::AES256_GCM_IV_STATIC)).and_return('hashed_value')
+
+ described_class.aes256_gcm_encrypt('some-value')
+ end
end
describe '.aes256_gcm_decrypt' do
- let(:encrypted) { described_class.aes256_gcm_encrypt('some-value') }
+ before do
+ stub_feature_flags(dynamic_nonce_creation: false)
+ end
+
+ context 'when token was encrypted using static nonce' do
+ let(:encrypted) { described_class.aes256_gcm_encrypt('some-value', nonce: described_class::AES256_GCM_IV_STATIC) }
+
+ it 'correctly decrypts encrypted string' do
+ decrypted = described_class.aes256_gcm_decrypt(encrypted)
+
+ expect(decrypted).to eq 'some-value'
+ end
+
+ it 'decrypts a value when it ends with a new line character' do
+ decrypted = described_class.aes256_gcm_decrypt(encrypted + "\n")
- it 'correctly decrypts encrypted string' do
- decrypted = described_class.aes256_gcm_decrypt(encrypted)
+ expect(decrypted).to eq 'some-value'
+ end
- expect(decrypted).to eq 'some-value'
+ it 'does not save hashed token with iv value in database' do
+ expect { described_class.aes256_gcm_decrypt(encrypted) }.not_to change { TokenWithIv.count }
+ end
+
+ context 'with feature flag switched on' do
+ before do
+ stub_feature_flags(dynamic_nonce_creation: true)
+ end
+
+ it 'correctly decrypts encrypted string' do
+ decrypted = described_class.aes256_gcm_decrypt(encrypted)
+
+ expect(decrypted).to eq 'some-value'
+ end
+ end
end
- it 'decrypts a value when it ends with a new line character' do
- decrypted = described_class.aes256_gcm_decrypt(encrypted + "\n")
+ context 'when token was encrypted using random nonce' do
+ let(:value) { 'random-value' }
+
+ # for compatibility with tokens encrypted using dynamic nonce
+ let!(:encrypted) do
+ iv = create_nonce
+ encrypted_token = described_class.create_encrypted_token(value, iv)
+ TokenWithIv.create!(hashed_token: Digest::SHA256.digest(encrypted_token), hashed_plaintext_token: Digest::SHA256.digest(encrypted_token), iv: iv)
+ encrypted_token
+ end
+
+ before do
+ stub_feature_flags(dynamic_nonce_creation: true)
+ end
- expect(decrypted).to eq 'some-value'
+ it 'correctly decrypts encrypted string' do
+ decrypted = described_class.aes256_gcm_decrypt(encrypted)
+
+ expect(decrypted).to eq value
+ end
+
+ it 'does not save hashed token with iv value in database' do
+ expect { described_class.aes256_gcm_decrypt(encrypted) }.not_to change { TokenWithIv.count }
+ end
end
end
+
+ def create_nonce
+ cipher = OpenSSL::Cipher.new('aes-256-gcm')
+ cipher.encrypt # Required before '#random_iv' can be called
+ cipher.random_iv # Ensures that the IV is the correct length respective to the algorithm used.
+ end
end
diff --git a/spec/lib/gitlab/current_settings_spec.rb b/spec/lib/gitlab/current_settings_spec.rb
index 786db23ffc4..01aceec12c5 100644
--- a/spec/lib/gitlab/current_settings_spec.rb
+++ b/spec/lib/gitlab/current_settings_spec.rb
@@ -194,4 +194,32 @@ RSpec.describe Gitlab::CurrentSettings do
end
end
end
+
+ describe '#current_application_settings?', :use_clean_rails_memory_store_caching do
+ before do
+ allow(Gitlab::CurrentSettings).to receive(:current_application_settings?).and_call_original
+ end
+
+ it 'returns true when settings exist' do
+ create(:application_setting,
+ home_page_url: 'http://mydomain.com',
+ signup_enabled: false)
+
+ expect(described_class.current_application_settings?).to eq(true)
+ end
+
+ it 'returns false when settings do not exist' do
+ expect(described_class.current_application_settings?).to eq(false)
+ end
+
+ context 'with cache', :request_store do
+ include_context 'with settings in cache'
+
+ it 'returns an in-memory ApplicationSetting object' do
+ expect(ApplicationSetting).not_to receive(:current)
+
+ expect(described_class.current_application_settings?).to eq(true)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/danger/base_linter_spec.rb b/spec/lib/gitlab/danger/base_linter_spec.rb
deleted file mode 100644
index 0136a0278ae..00000000000
--- a/spec/lib/gitlab/danger/base_linter_spec.rb
+++ /dev/null
@@ -1,193 +0,0 @@
-# frozen_string_literal: true
-
-require 'fast_spec_helper'
-require 'rspec-parameterized'
-require_relative 'danger_spec_helper'
-
-require 'gitlab/danger/base_linter'
-
-RSpec.describe Gitlab::Danger::BaseLinter do
- let(:commit_class) do
- Struct.new(:message, :sha, :diff_parent)
- end
-
- let(:commit_message) { 'A commit message' }
- let(:commit) { commit_class.new(commit_message, anything, anything) }
-
- subject(:commit_linter) { described_class.new(commit) }
-
- describe '#failed?' do
- context 'with no failures' do
- it { expect(commit_linter).not_to be_failed }
- end
-
- context 'with failures' do
- before do
- commit_linter.add_problem(:subject_too_long, described_class.subject_description)
- end
-
- it { expect(commit_linter).to be_failed }
- end
- end
-
- describe '#add_problem' do
- it 'stores messages in #failures' do
- commit_linter.add_problem(:subject_too_long, '%s')
-
- expect(commit_linter.problems).to eq({ subject_too_long: described_class.problems_mapping[:subject_too_long] })
- end
- end
-
- shared_examples 'a valid commit' do
- it 'does not have any problem' do
- commit_linter.lint_subject
-
- expect(commit_linter.problems).to be_empty
- end
- end
-
- describe '#lint_subject' do
- context 'when subject valid' do
- it_behaves_like 'a valid commit'
- end
-
- context 'when subject is too short' do
- let(:commit_message) { 'A B' }
-
- it 'adds a problem' do
- expect(commit_linter).to receive(:add_problem).with(:subject_too_short, described_class.subject_description)
-
- commit_linter.lint_subject
- end
- end
-
- context 'when subject is too long' do
- let(:commit_message) { 'A B ' + 'C' * described_class::MAX_LINE_LENGTH }
-
- it 'adds a problem' do
- expect(commit_linter).to receive(:add_problem).with(:subject_too_long, described_class.subject_description)
-
- commit_linter.lint_subject
- end
- end
-
- context 'when ignoring length issues for subject having not-ready wording' do
- using RSpec::Parameterized::TableSyntax
-
- let(:final_message) { 'A B C' }
-
- 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
-
- 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 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
- expect(commit_linter).to receive(:add_problem).with(:subject_too_short, described_class.subject_description)
- expect(commit_linter).to receive(:add_problem).with(:subject_too_long, described_class.subject_description)
-
- commit_linter.lint_subject
- end
- end
-
- context 'when subject starts with lowercase' do
- let(:commit_message) { 'a B C' }
-
- it 'adds a problem' do
- expect(commit_linter).to receive(:add_problem).with(:subject_starts_with_lowercase, described_class.subject_description)
-
- commit_linter.lint_subject
- end
- end
-
- [
- '[ci skip] A commit message',
- '[Ci skip] A commit message',
- '[API] A commit message',
- 'api: A commit message',
- 'API: A commit message',
- 'API: a commit message',
- 'API: a commit message'
- ].each do |message|
- context "when subject is '#{message}'" do
- let(:commit_message) { message }
-
- it 'does not add a problem' do
- expect(commit_linter).not_to receive(:add_problem)
-
- commit_linter.lint_subject
- end
- end
- end
-
- [
- '[ci skip]A commit message',
- '[Ci skip] A commit message',
- '[ci skip] a commit message',
- 'api: a commit message',
- '! A commit message'
- ].each do |message|
- context "when subject is '#{message}'" do
- let(:commit_message) { message }
-
- it 'adds a problem' do
- expect(commit_linter).to receive(:add_problem).with(:subject_starts_with_lowercase, described_class.subject_description)
-
- commit_linter.lint_subject
- end
- end
- end
-
- context 'when subject ends with a period' do
- let(:commit_message) { 'A B C.' }
-
- it 'adds a problem' do
- expect(commit_linter).to receive(:add_problem).with(:subject_ends_with_a_period, described_class.subject_description)
-
- commit_linter.lint_subject
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/danger/changelog_spec.rb b/spec/lib/gitlab/danger/changelog_spec.rb
deleted file mode 100644
index 04c515f1205..00000000000
--- a/spec/lib/gitlab/danger/changelog_spec.rb
+++ /dev/null
@@ -1,229 +0,0 @@
-# frozen_string_literal: true
-
-require 'fast_spec_helper'
-require_relative 'danger_spec_helper'
-
-require 'gitlab/danger/changelog'
-
-RSpec.describe Gitlab::Danger::Changelog do
- include DangerSpecHelper
-
- let(:added_files) { nil }
- let(:fake_git) { double('fake-git', added_files: added_files) }
-
- let(:mr_labels) { nil }
- let(:mr_json) { nil }
- let(:fake_gitlab) { double('fake-gitlab', mr_labels: mr_labels, mr_json: mr_json) }
-
- let(:changes_by_category) { nil }
- let(:sanitize_mr_title) { nil }
- let(:ee?) { false }
- let(:fake_helper) { double('fake-helper', changes_by_category: changes_by_category, sanitize_mr_title: sanitize_mr_title, ee?: ee?) }
-
- let(:fake_danger) { new_fake_danger.include(described_class) }
-
- subject(:changelog) { fake_danger.new(git: fake_git, gitlab: fake_gitlab, helper: fake_helper) }
-
- describe '#required?' do
- subject { changelog.required? }
-
- context 'added files contain a migration' do
- [
- 'db/migrate/20200000000000_new_migration.rb',
- 'db/post_migrate/20200000000000_new_migration.rb'
- ].each do |file_path|
- let(:added_files) { [file_path] }
-
- it { is_expected.to be_truthy }
- end
- end
-
- context 'added files do not contain a migration' do
- [
- 'app/models/model.rb',
- 'app/assets/javascripts/file.js'
- ].each do |file_path|
- let(:added_files) { [file_path] }
-
- it { is_expected.to be_falsey }
- end
- end
- end
-
- describe '#optional?' do
- let(:category_with_changelog) { :backend }
- let(:label_with_changelog) { 'frontend' }
- let(:category_without_changelog) { Gitlab::Danger::Changelog::NO_CHANGELOG_CATEGORIES.first }
- let(:label_without_changelog) { Gitlab::Danger::Changelog::NO_CHANGELOG_LABELS.first }
-
- subject { changelog.optional? }
-
- context 'when MR contains only categories requiring no changelog' do
- let(:changes_by_category) { { category_without_changelog => nil } }
- let(:mr_labels) { [] }
-
- it 'is falsey' do
- is_expected.to be_falsy
- end
- end
-
- context 'when MR contains a label that require no changelog' do
- let(:changes_by_category) { { category_with_changelog => nil } }
- let(:mr_labels) { [label_with_changelog, label_without_changelog] }
-
- it 'is falsey' do
- is_expected.to be_falsy
- end
- end
-
- context 'when MR contains a category that require changelog and a category that require no changelog' do
- let(:changes_by_category) { { category_with_changelog => nil, category_without_changelog => nil } }
- let(:mr_labels) { [] }
-
- it 'is truthy' do
- is_expected.to be_truthy
- end
- end
-
- context 'when MR contains a category that require changelog and a category that require no changelog with changelog label' do
- let(:changes_by_category) { { category_with_changelog => nil, category_without_changelog => nil } }
- let(:mr_labels) { ['feature'] }
-
- it 'is truthy' do
- is_expected.to be_truthy
- end
- end
-
- context 'when MR contains a category that require changelog and a category that require no changelog with no changelog label' do
- let(:changes_by_category) { { category_with_changelog => nil, category_without_changelog => nil } }
- let(:mr_labels) { ['tooling'] }
-
- it 'is truthy' do
- is_expected.to be_falsey
- end
- end
- end
-
- describe '#found' do
- subject { changelog.found }
-
- context 'added files contain a changelog' do
- [
- 'changelogs/unreleased/entry.yml',
- 'ee/changelogs/unreleased/entry.yml'
- ].each do |file_path|
- let(:added_files) { [file_path] }
-
- it { is_expected.to be_truthy }
- end
- end
-
- context 'added files do not contain a changelog' do
- [
- 'app/models/model.rb',
- 'app/assets/javascripts/file.js'
- ].each do |file_path|
- let(:added_files) { [file_path] }
- it { is_expected.to eq(nil) }
- end
- end
- end
-
- describe '#ee_changelog?' do
- subject { changelog.ee_changelog? }
-
- before do
- allow(changelog).to receive(:found).and_return(file_path)
- end
-
- context 'is ee changelog' do
- let(:file_path) { 'ee/changelogs/unreleased/entry.yml' }
-
- it { is_expected.to be_truthy }
- end
-
- context 'is not ee changelog' do
- let(:file_path) { 'changelogs/unreleased/entry.yml' }
-
- it { is_expected.to be_falsy }
- end
- end
-
- describe '#modified_text' do
- let(:mr_json) { { "iid" => 1234, "title" => sanitize_mr_title } }
-
- subject { changelog.modified_text }
-
- 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(:mr_json) { { "iid" => 1234, "title" => sanitize_mr_title } }
-
- subject { changelog.required_text }
-
- 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(:mr_json) { { "iid" => 1234, "title" => sanitize_mr_title } }
-
- subject { changelog.optional_text }
-
- 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/commit_linter_spec.rb b/spec/lib/gitlab/danger/commit_linter_spec.rb
deleted file mode 100644
index d3d86037a53..00000000000
--- a/spec/lib/gitlab/danger/commit_linter_spec.rb
+++ /dev/null
@@ -1,242 +0,0 @@
-# frozen_string_literal: true
-
-require 'fast_spec_helper'
-require 'rspec-parameterized'
-require_relative 'danger_spec_helper'
-
-require 'gitlab/danger/commit_linter'
-
-RSpec.describe Gitlab::Danger::CommitLinter do
- using RSpec::Parameterized::TableSyntax
-
- let(:total_files_changed) { 2 }
- let(:total_lines_changed) { 10 }
- let(:stats) { { total: { files: total_files_changed, lines: total_lines_changed } } }
- let(:diff_parent) { Struct.new(:stats).new(stats) }
- let(:commit_class) do
- Struct.new(:message, :sha, :diff_parent)
- end
-
- let(:commit_message) { 'A commit message' }
- let(:commit_sha) { 'abcd1234' }
- let(:commit) { commit_class.new(commit_message, commit_sha, diff_parent) }
-
- subject(:commit_linter) { described_class.new(commit) }
-
- describe '#fixup?' do
- where(:commit_message, :is_fixup) do
- 'A commit message' | false
- 'fixup!' | true
- 'fixup! A commit message' | true
- 'squash!' | true
- 'squash! A commit message' | true
- end
-
- with_them do
- it 'is true when commit message starts with "fixup!" or "squash!"' do
- expect(commit_linter.fixup?).to be(is_fixup)
- end
- end
- end
-
- describe '#suggestion?' do
- where(:commit_message, :is_suggestion) do
- 'A commit message' | false
- 'Apply suggestion to' | true
- 'Apply suggestion to "A commit message"' | true
- end
-
- with_them do
- it 'is true when commit message starts with "Apply suggestion to"' do
- expect(commit_linter.suggestion?).to be(is_suggestion)
- end
- end
- end
-
- describe '#merge?' do
- where(:commit_message, :is_merge) do
- 'A commit message' | false
- 'Merge branch' | true
- 'Merge branch "A commit message"' | true
- end
-
- with_them do
- it 'is true when commit message starts with "Merge branch"' do
- expect(commit_linter.merge?).to be(is_merge)
- end
- end
- end
-
- describe '#revert?' do
- where(:commit_message, :is_revert) do
- 'A commit message' | false
- 'Revert' | false
- 'Revert "' | true
- 'Revert "A commit message"' | true
- end
-
- with_them do
- it 'is true when commit message starts with "Revert \""' do
- expect(commit_linter.revert?).to be(is_revert)
- end
- end
- end
-
- describe '#multi_line?' do
- where(:commit_message, :is_multi_line) do
- "A commit message" | false
- "A commit message\n" | false
- "A commit message\n\n" | false
- "A commit message\n\nSigned-off-by: User Name <user@name.me>" | false
- "A commit message\n\nWith details" | true
- end
-
- with_them do
- it 'is true when commit message contains details' do
- expect(commit_linter.multi_line?).to be(is_multi_line)
- end
- end
- end
-
- shared_examples 'a valid commit' do
- it 'does not have any problem' do
- commit_linter.lint
-
- expect(commit_linter.problems).to be_empty
- end
- end
-
- describe '#lint' do
- describe 'separator' do
- context 'when separator is missing' do
- let(:commit_message) { "A B C\n" }
-
- it_behaves_like 'a valid commit'
- end
-
- context 'when separator is a blank line' do
- let(:commit_message) { "A B C\n\nMore details." }
-
- it_behaves_like 'a valid commit'
- end
-
- context 'when separator is missing' do
- let(:commit_message) { "A B C\nMore details." }
-
- it 'adds a problem' do
- expect(commit_linter).to receive(:add_problem).with(:separator_missing)
-
- commit_linter.lint
- end
- end
- end
-
- describe 'details' do
- context 'when details are valid' do
- let(:commit_message) { "A B C\n\nMore details." }
-
- it_behaves_like 'a valid commit'
- end
-
- context 'when no details are given and many files are changed' do
- let(:total_files_changed) { described_class::MAX_CHANGED_FILES_IN_COMMIT + 1 }
-
- it_behaves_like 'a valid commit'
- end
-
- context 'when no details are given and many lines are changed' do
- let(:total_lines_changed) { described_class::MAX_CHANGED_LINES_IN_COMMIT + 1 }
-
- it_behaves_like 'a valid commit'
- end
-
- context 'when no details are given and many files and lines are changed' do
- let(:total_files_changed) { described_class::MAX_CHANGED_FILES_IN_COMMIT + 1 }
- let(:total_lines_changed) { described_class::MAX_CHANGED_LINES_IN_COMMIT + 1 }
-
- it 'adds a problem' do
- expect(commit_linter).to receive(:add_problem).with(:details_too_many_changes)
-
- commit_linter.lint
- end
- end
-
- context 'when details exceeds the max line length' do
- let(:commit_message) { "A B C\n\n" + 'D' * (described_class::MAX_LINE_LENGTH + 1) }
-
- it 'adds a problem' do
- expect(commit_linter).to receive(:add_problem).with(:details_line_too_long)
-
- commit_linter.lint
- end
- end
-
- context 'when details exceeds the max line length including URLs' do
- let(:commit_message) do
- "A B C\n\nsome message with https://example.com and https://gitlab.com" + 'D' * described_class::MAX_LINE_LENGTH
- end
-
- it_behaves_like 'a valid commit'
- end
- end
-
- describe 'message' do
- context 'when message includes a text emoji' do
- let(:commit_message) { "A commit message :+1:" }
-
- it 'adds a problem' do
- expect(commit_linter).to receive(:add_problem).with(:message_contains_text_emoji)
-
- commit_linter.lint
- end
- end
-
- context 'when message includes a unicode emoji' do
- let(:commit_message) { "A commit message 🚀" }
-
- it 'adds a problem' do
- expect(commit_linter).to receive(:add_problem).with(:message_contains_unicode_emoji)
-
- commit_linter.lint
- end
- end
-
- context 'when message includes a value that is surrounded by backticks' do
- let(:commit_message) { "A commit message `%20`" }
-
- it 'does not add a problem' do
- expect(commit_linter).not_to receive(:add_problem)
-
- commit_linter.lint
- end
- end
-
- context 'when message includes a short reference' do
- [
- 'A commit message to fix #1234',
- 'A commit message to fix !1234',
- 'A commit message to fix &1234',
- 'A commit message to fix %1234',
- 'A commit message to fix gitlab#1234',
- 'A commit message to fix gitlab!1234',
- 'A commit message to fix gitlab&1234',
- 'A commit message to fix gitlab%1234',
- 'A commit message to fix gitlab-org/gitlab#1234',
- 'A commit message to fix gitlab-org/gitlab!1234',
- 'A commit message to fix gitlab-org/gitlab&1234',
- 'A commit message to fix gitlab-org/gitlab%1234',
- 'A commit message to fix "gitlab-org/gitlab%1234"',
- 'A commit message to fix `gitlab-org/gitlab%1234'
- ].each do |message|
- let(:commit_message) { message }
-
- it 'adds a problem' do
- expect(commit_linter).to receive(:add_problem).with(:message_contains_short_reference)
-
- commit_linter.lint
- end
- end
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/danger/danger_spec_helper.rb b/spec/lib/gitlab/danger/danger_spec_helper.rb
deleted file mode 100644
index b1e84b3c13d..00000000000
--- a/spec/lib/gitlab/danger/danger_spec_helper.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# frozen_string_literal: true
-
-module DangerSpecHelper
- def new_fake_danger
- Class.new do
- attr_reader :git, :gitlab, :helper
-
- # rubocop:disable Gitlab/ModuleWithInstanceVariables
- def initialize(git: nil, gitlab: nil, helper: nil)
- @git = git
- @gitlab = gitlab
- @helper = helper
- end
- # rubocop:enable Gitlab/ModuleWithInstanceVariables
- end
- end
-end
diff --git a/spec/lib/gitlab/danger/emoji_checker_spec.rb b/spec/lib/gitlab/danger/emoji_checker_spec.rb
deleted file mode 100644
index 6092c751e1c..00000000000
--- a/spec/lib/gitlab/danger/emoji_checker_spec.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-# frozen_string_literal: true
-
-require 'fast_spec_helper'
-require 'rspec-parameterized'
-
-require 'gitlab/danger/emoji_checker'
-
-RSpec.describe Gitlab::Danger::EmojiChecker do
- using RSpec::Parameterized::TableSyntax
-
- describe '#includes_text_emoji?' do
- where(:text, :includes_emoji) do
- 'Hello World!' | false
- ':+1:' | true
- 'Hello World! :+1:' | true
- end
-
- with_them do
- it 'is true when text includes a text emoji' do
- expect(subject.includes_text_emoji?(text)).to be(includes_emoji)
- end
- end
- end
-
- describe '#includes_unicode_emoji?' do
- where(:text, :includes_emoji) do
- 'Hello World!' | false
- '🚀' | true
- 'Hello World! 🚀' | true
- end
-
- with_them do
- it 'is true when text includes a text emoji' do
- expect(subject.includes_unicode_emoji?(text)).to be(includes_emoji)
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/danger/helper_spec.rb b/spec/lib/gitlab/danger/helper_spec.rb
deleted file mode 100644
index bd5c746dd54..00000000000
--- a/spec/lib/gitlab/danger/helper_spec.rb
+++ /dev/null
@@ -1,602 +0,0 @@
-# frozen_string_literal: true
-
-require 'fast_spec_helper'
-require 'rspec-parameterized'
-require_relative 'danger_spec_helper'
-
-require 'gitlab/danger/helper'
-
-RSpec.describe Gitlab::Danger::Helper do
- using RSpec::Parameterized::TableSyntax
- include DangerSpecHelper
-
- let(:fake_git) { double('fake-git') }
-
- let(:mr_author) { nil }
- let(:fake_gitlab) { double('fake-gitlab', mr_author: mr_author) }
-
- let(:fake_danger) { new_fake_danger.include(described_class) }
-
- subject(:helper) { fake_danger.new(git: fake_git, gitlab: fake_gitlab) }
-
- describe '#gitlab_helper' do
- context 'when gitlab helper is not available' do
- let(:fake_gitlab) { nil }
-
- it 'returns nil' do
- expect(helper.gitlab_helper).to be_nil
- end
- end
-
- context 'when gitlab helper is available' do
- it 'returns the gitlab helper' do
- expect(helper.gitlab_helper).to eq(fake_gitlab)
- end
- end
-
- context 'when danger gitlab plugin is not available' do
- it 'returns nil' do
- invalid_danger = Class.new do
- include Gitlab::Danger::Helper
- end.new
-
- expect(invalid_danger.gitlab_helper).to be_nil
- end
- end
- end
-
- describe '#release_automation?' do
- context 'when gitlab helper is not available' do
- it 'returns false' do
- expect(helper.release_automation?).to be_falsey
- end
- end
-
- context 'when gitlab helper is available' do
- context "but the MR author isn't the RELEASE_TOOLS_BOT" do
- let(:mr_author) { 'johnmarston' }
-
- it 'returns false' do
- expect(helper.release_automation?).to be_falsey
- end
- end
-
- context 'and the MR author is the RELEASE_TOOLS_BOT' do
- let(:mr_author) { described_class::RELEASE_TOOLS_BOT }
-
- it 'returns true' do
- expect(helper.release_automation?).to be_truthy
- end
- end
- end
- end
-
- describe '#all_changed_files' do
- subject { helper.all_changed_files }
-
- it 'interprets a list of changes from the danger git plugin' do
- expect(fake_git).to receive(:added_files) { %w[a b c.old] }
- expect(fake_git).to receive(:modified_files) { %w[d e] }
- expect(fake_git)
- .to receive(:renamed_files)
- .at_least(:once)
- .and_return([{ before: 'c.old', after: 'c.new' }])
-
- is_expected.to contain_exactly('a', 'b', 'c.new', 'd', 'e')
- end
- end
-
- describe '#changed_lines' do
- subject { helper.changed_lines('changed_file.rb') }
-
- before do
- allow(fake_git).to receive(:diff_for_file).with('changed_file.rb').and_return(diff)
- end
-
- context 'when file has diff' do
- let(:diff) { double(:diff, patch: "+ # New change here\n+ # New change there") }
-
- it 'returns file changes' do
- is_expected.to eq(['+ # New change here', '+ # New change there'])
- end
- end
-
- context 'when file has no diff (renamed without changes)' do
- let(:diff) { nil }
-
- it 'returns a blank array' do
- is_expected.to eq([])
- end
- end
- end
-
- describe "changed_files" do
- it 'returns list of changed files matching given regex' do
- expect(helper).to receive(:all_changed_files).and_return(%w[migration.rb usage_data.rb])
-
- expect(helper.changed_files(/usage_data/)).to contain_exactly('usage_data.rb')
- end
- end
-
- describe '#all_ee_changes' do
- subject { helper.all_ee_changes }
-
- it 'returns all changed files starting with ee/' do
- expect(helper).to receive(:all_changed_files).and_return(%w[fr/ee/beer.rb ee/wine.rb ee/lib/ido.rb ee.k])
-
- is_expected.to match_array(%w[ee/wine.rb ee/lib/ido.rb])
- end
- end
-
- describe '#ee?' do
- subject { helper.ee? }
-
- it 'returns true if CI_PROJECT_NAME if set to gitlab' do
- stub_env('CI_PROJECT_NAME', 'gitlab')
- expect(Dir).not_to receive(:exist?)
-
- is_expected.to be_truthy
- end
-
- it 'delegates to CHANGELOG-EE.md existence if CI_PROJECT_NAME is set to something else' do
- stub_env('CI_PROJECT_NAME', 'something else')
- expect(Dir).to receive(:exist?).with(File.expand_path('../../../../ee', __dir__)) { true }
-
- is_expected.to be_truthy
- end
-
- it 'returns true if ee exists' do
- stub_env('CI_PROJECT_NAME', nil)
- expect(Dir).to receive(:exist?).with(File.expand_path('../../../../ee', __dir__)) { true }
-
- is_expected.to be_truthy
- end
-
- it "returns false if ee doesn't exist" do
- stub_env('CI_PROJECT_NAME', nil)
- expect(Dir).to receive(:exist?).with(File.expand_path('../../../../ee', __dir__)) { false }
-
- is_expected.to be_falsy
- end
- end
-
- describe '#project_name' do
- subject { helper.project_name }
-
- it 'returns gitlab if ee? returns true' do
- expect(helper).to receive(:ee?) { true }
-
- is_expected.to eq('gitlab')
- end
-
- it 'returns gitlab-ce if ee? returns false' do
- expect(helper).to receive(:ee?) { false }
-
- is_expected.to eq('gitlab-foss')
- end
- end
-
- describe '#markdown_list' do
- it 'creates a markdown list of items' do
- items = %w[a b]
-
- expect(helper.markdown_list(items)).to eq("* `a`\n* `b`")
- end
-
- it 'wraps items in <details> when there are more than 10 items' do
- items = ('a'..'k').to_a
-
- expect(helper.markdown_list(items)).to match(%r{<details>[^<]+</details>})
- end
- end
-
- describe '#changes_by_category' do
- it 'categorizes changed files' do
- expect(fake_git).to receive(:added_files) { %w[foo foo.md foo.rb foo.js db/migrate/foo lib/gitlab/database/foo.rb qa/foo ee/changelogs/foo.yml] }
- allow(fake_git).to receive(:modified_files) { [] }
- allow(fake_git).to receive(:renamed_files) { [] }
-
- expect(helper.changes_by_category).to eq(
- backend: %w[foo.rb],
- database: %w[db/migrate/foo lib/gitlab/database/foo.rb],
- frontend: %w[foo.js],
- none: %w[ee/changelogs/foo.yml foo.md],
- qa: %w[qa/foo],
- unknown: %w[foo]
- )
- end
- end
-
- describe '#categories_for_file' do
- before do
- allow(fake_git).to receive(:diff_for_file).with('usage_data.rb') { double(:diff, patch: "+ count(User.active)") }
- end
-
- where(:path, :expected_categories) do
- 'usage_data.rb' | [:database, :backend]
- 'doc/foo.md' | [:docs]
- 'CONTRIBUTING.md' | [:docs]
- 'LICENSE' | [:docs]
- 'MAINTENANCE.md' | [:docs]
- 'PHILOSOPHY.md' | [:docs]
- 'PROCESS.md' | [:docs]
- 'README.md' | [:docs]
-
- 'ee/doc/foo' | [:unknown]
- 'ee/README' | [:unknown]
-
- 'app/assets/foo' | [:frontend]
- 'app/views/foo' | [:frontend]
- 'public/foo' | [:frontend]
- 'scripts/frontend/foo' | [:frontend]
- 'spec/javascripts/foo' | [:frontend]
- 'spec/frontend/bar' | [:frontend]
- 'vendor/assets/foo' | [:frontend]
- 'babel.config.js' | [:frontend]
- 'jest.config.js' | [:frontend]
- 'package.json' | [:frontend]
- 'yarn.lock' | [:frontend]
- 'config/foo.js' | [:frontend]
- 'config/deep/foo.js' | [:frontend]
-
- 'ee/app/assets/foo' | [:frontend]
- 'ee/app/views/foo' | [:frontend]
- 'ee/spec/javascripts/foo' | [:frontend]
- 'ee/spec/frontend/bar' | [:frontend]
-
- '.gitlab/ci/frontend.gitlab-ci.yml' | %i[frontend engineering_productivity]
-
- 'app/models/foo' | [:backend]
- 'bin/foo' | [:backend]
- 'config/foo' | [:backend]
- 'lib/foo' | [:backend]
- 'rubocop/foo' | [:backend]
- '.rubocop.yml' | [:backend]
- '.rubocop_todo.yml' | [:backend]
- '.rubocop_manual_todo.yml' | [:backend]
- 'spec/foo' | [:backend]
- 'spec/foo/bar' | [:backend]
-
- 'ee/app/foo' | [:backend]
- 'ee/bin/foo' | [:backend]
- 'ee/spec/foo' | [:backend]
- 'ee/spec/foo/bar' | [:backend]
-
- 'spec/features/foo' | [:test]
- 'ee/spec/features/foo' | [:test]
- 'spec/support/shared_examples/features/foo' | [:test]
- 'ee/spec/support/shared_examples/features/foo' | [:test]
- 'spec/support/shared_contexts/features/foo' | [:test]
- 'ee/spec/support/shared_contexts/features/foo' | [:test]
- 'spec/support/helpers/features/foo' | [:test]
- 'ee/spec/support/helpers/features/foo' | [:test]
-
- 'generator_templates/foo' | [:backend]
- 'vendor/languages.yml' | [:backend]
- 'file_hooks/examples/' | [:backend]
-
- 'Gemfile' | [:backend]
- 'Gemfile.lock' | [:backend]
- 'Rakefile' | [:backend]
- 'FOO_VERSION' | [:backend]
-
- 'Dangerfile' | [:engineering_productivity]
- 'danger/commit_messages/Dangerfile' | [:engineering_productivity]
- 'ee/danger/commit_messages/Dangerfile' | [:engineering_productivity]
- 'danger/commit_messages/' | [:engineering_productivity]
- 'ee/danger/commit_messages/' | [:engineering_productivity]
- '.gitlab-ci.yml' | [:engineering_productivity]
- '.gitlab/ci/cng.gitlab-ci.yml' | [:engineering_productivity]
- '.gitlab/ci/ee-specific-checks.gitlab-ci.yml' | [:engineering_productivity]
- 'scripts/foo' | [:engineering_productivity]
- 'lib/gitlab/danger/foo' | [:engineering_productivity]
- 'ee/lib/gitlab/danger/foo' | [:engineering_productivity]
- 'lefthook.yml' | [:engineering_productivity]
- '.editorconfig' | [:engineering_productivity]
- 'tooling/bin/find_foss_tests' | [:engineering_productivity]
- '.codeclimate.yml' | [:engineering_productivity]
- '.gitlab/CODEOWNERS' | [:engineering_productivity]
-
- 'lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml' | [:ci_template]
- 'lib/gitlab/ci/templates/dotNET-Core.yml' | [:ci_template]
-
- 'ee/FOO_VERSION' | [:unknown]
-
- 'db/schema.rb' | [:database]
- 'db/structure.sql' | [:database]
- 'db/migrate/foo' | [:database]
- 'db/post_migrate/foo' | [:database]
- 'ee/db/migrate/foo' | [:database]
- 'ee/db/post_migrate/foo' | [:database]
- 'ee/db/geo/migrate/foo' | [:database]
- 'ee/db/geo/post_migrate/foo' | [:database]
- 'app/models/project_authorization.rb' | [:database]
- 'app/services/users/refresh_authorized_projects_service.rb' | [:database]
- 'lib/gitlab/background_migration.rb' | [:database]
- 'lib/gitlab/background_migration/foo' | [:database]
- 'ee/lib/gitlab/background_migration/foo' | [:database]
- 'lib/gitlab/database.rb' | [:database]
- 'lib/gitlab/database/foo' | [:database]
- 'ee/lib/gitlab/database/foo' | [:database]
- 'lib/gitlab/github_import.rb' | [:database]
- 'lib/gitlab/github_import/foo' | [:database]
- 'lib/gitlab/sql/foo' | [:database]
- 'rubocop/cop/migration/foo' | [:database]
-
- 'db/fixtures/foo.rb' | [:backend]
- 'ee/db/fixtures/foo.rb' | [:backend]
- 'doc/api/graphql/reference/gitlab_schema.graphql' | [:backend]
- 'doc/api/graphql/reference/gitlab_schema.json' | [:backend]
-
- 'qa/foo' | [:qa]
- 'ee/qa/foo' | [:qa]
-
- 'changelogs/foo' | [:none]
- 'ee/changelogs/foo' | [:none]
- 'locale/gitlab.pot' | [:none]
-
- 'FOO' | [:unknown]
- 'foo' | [:unknown]
-
- 'foo/bar.rb' | [:backend]
- 'foo/bar.js' | [:frontend]
- 'foo/bar.txt' | [:none]
- 'foo/bar.md' | [:none]
- end
-
- with_them do
- subject { helper.categories_for_file(path) }
-
- it { is_expected.to eq(expected_categories) }
- end
-
- context 'having specific changes' do
- 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
-
- 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(file)).to eq(expected_categories)
- end
- end
- end
- end
- end
-
- describe '#label_for_category' do
- where(:category, :expected_label) do
- :backend | '~backend'
- :database | '~database'
- :docs | '~documentation'
- :foo | '~foo'
- :frontend | '~frontend'
- :none | ''
- :qa | '~QA'
- :engineering_productivity | '~"Engineering Productivity" for CI, Danger'
- :ci_template | '~"ci::templates"'
- end
-
- with_them do
- subject { helper.label_for_category(category) }
-
- it { is_expected.to eq(expected_label) }
- end
- end
-
- describe '#new_teammates' do
- it 'returns an array of Teammate' do
- usernames = %w[filipa iamphil]
-
- teammates = helper.new_teammates(usernames)
-
- expect(teammates.map(&:username)).to eq(usernames)
- end
- end
-
- describe '#security_mr?' do
- it 'returns false when `gitlab_helper` is unavailable' do
- expect(helper).to receive(:gitlab_helper).and_return(nil)
-
- expect(helper).not_to be_security_mr
- end
-
- it 'returns false when on a normal merge request' do
- expect(fake_gitlab).to receive(:mr_json)
- .and_return('web_url' => 'https://gitlab.com/gitlab-org/gitlab/-/merge_requests/1')
-
- expect(helper).not_to be_security_mr
- end
-
- it 'returns true when on a security merge request' do
- expect(fake_gitlab).to receive(:mr_json)
- .and_return('web_url' => 'https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/1')
-
- expect(helper).to be_security_mr
- end
- end
-
- describe '#draft_mr?' do
- it 'returns false when `gitlab_helper` is unavailable' do
- expect(helper).to receive(:gitlab_helper).and_return(nil)
-
- expect(helper).not_to be_draft_mr
- end
-
- it 'returns true for a draft MR' do
- expect(fake_gitlab).to receive(:mr_json)
- .and_return('title' => 'Draft: My MR title')
-
- expect(helper).to be_draft_mr
- end
-
- it 'returns false for non draft MR' do
- expect(fake_gitlab).to receive(:mr_json)
- .and_return('title' => 'My MR title')
-
- expect(helper).not_to be_draft_mr
- end
- end
-
- describe '#cherry_pick_mr?' do
- it 'returns false when `gitlab_helper` is unavailable' do
- expect(helper).to receive(:gitlab_helper).and_return(nil)
-
- expect(helper).not_to be_cherry_pick_mr
- end
-
- context 'when MR title does not mention a cherry-pick' do
- it 'returns false' do
- expect(fake_gitlab).to receive(:mr_json)
- .and_return('title' => 'Add feature xyz')
-
- expect(helper).not_to be_cherry_pick_mr
- end
- end
-
- context 'when MR title mentions a cherry-pick' do
- [
- 'Cherry Pick !1234',
- 'cherry-pick !1234',
- 'CherryPick !1234'
- ].each do |mr_title|
- it 'returns true' do
- expect(fake_gitlab).to receive(:mr_json)
- .and_return('title' => mr_title)
-
- expect(helper).to be_cherry_pick_mr
- end
- end
- end
- end
-
- describe '#stable_branch?' do
- it 'returns false when `gitlab_helper` is unavailable' do
- expect(helper).to receive(:gitlab_helper).and_return(nil)
-
- expect(helper).not_to be_stable_branch
- end
-
- context 'when MR target branch is not a stable branch' do
- it 'returns false' do
- expect(fake_gitlab).to receive(:mr_json)
- .and_return('target_branch' => 'my-feature-branch')
-
- expect(helper).not_to be_stable_branch
- end
- end
-
- context 'when MR target branch is a stable branch' do
- %w[
- 13-1-stable-ee
- 13-1-stable-ee-patch-1
- ].each do |target_branch|
- it 'returns true' do
- expect(fake_gitlab).to receive(:mr_json)
- .and_return('target_branch' => target_branch)
-
- expect(helper).to be_stable_branch
- end
- end
- end
- end
-
- describe '#mr_has_label?' do
- it 'returns false when `gitlab_helper` is unavailable' do
- expect(helper).to receive(:gitlab_helper).and_return(nil)
-
- expect(helper.mr_has_labels?('telemetry')).to be_falsey
- end
-
- context 'when mr has labels' do
- before do
- mr_labels = ['telemetry', 'telemetry::reviewed']
- expect(fake_gitlab).to receive(:mr_labels).and_return(mr_labels)
- end
-
- it 'returns true with a matched label' do
- expect(helper.mr_has_labels?('telemetry')).to be_truthy
- end
-
- it 'returns false with unmatched label' do
- expect(helper.mr_has_labels?('database')).to be_falsey
- end
-
- it 'returns true with an array of labels' do
- expect(helper.mr_has_labels?(['telemetry', 'telemetry::reviewed'])).to be_truthy
- end
-
- it 'returns true with multi arguments with matched labels' do
- expect(helper.mr_has_labels?('telemetry', 'telemetry::reviewed')).to be_truthy
- end
-
- it 'returns false with multi arguments with unmatched labels' do
- expect(helper.mr_has_labels?('telemetry', 'telemetry::non existing')).to be_falsey
- end
- end
- end
-
- describe '#labels_list' do
- let(:labels) { ['telemetry', 'telemetry::reviewed'] }
-
- it 'composes the labels string' do
- expect(helper.labels_list(labels)).to eq('~"telemetry", ~"telemetry::reviewed"')
- end
-
- context 'when passing a separator' do
- it 'composes the labels string with the given separator' do
- expect(helper.labels_list(labels, sep: ' ')).to eq('~"telemetry" ~"telemetry::reviewed"')
- end
- end
-
- it 'returns empty string for empty array' do
- expect(helper.labels_list([])).to eq('')
- end
- end
-
- describe '#prepare_labels_for_mr' do
- it 'composes the labels string' do
- mr_labels = ['telemetry', 'telemetry::reviewed']
-
- expect(helper.prepare_labels_for_mr(mr_labels)).to eq('/label ~"telemetry" ~"telemetry::reviewed"')
- end
-
- it 'returns empty string for empty array' do
- expect(helper.prepare_labels_for_mr([])).to eq('')
- end
- end
-
- describe '#has_ci_changes?' do
- context 'when .gitlab/ci is changed' do
- it 'returns true' do
- expect(helper).to receive(:all_changed_files).and_return(%w[migration.rb .gitlab/ci/test.yml])
-
- expect(helper.has_ci_changes?).to be_truthy
- end
- end
-
- context 'when .gitlab-ci.yml is changed' do
- it 'returns true' do
- expect(helper).to receive(:all_changed_files).and_return(%w[migration.rb .gitlab-ci.yml])
-
- expect(helper.has_ci_changes?).to be_truthy
- end
- end
-
- context 'when neither .gitlab/ci/ or .gitlab-ci.yml is changed' do
- it 'returns false' do
- expect(helper).to receive(:all_changed_files).and_return(%w[migration.rb nested/.gitlab-ci.yml])
-
- expect(helper.has_ci_changes?).to be_falsey
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/danger/merge_request_linter_spec.rb b/spec/lib/gitlab/danger/merge_request_linter_spec.rb
deleted file mode 100644
index 29facc9fdd6..00000000000
--- a/spec/lib/gitlab/danger/merge_request_linter_spec.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-# frozen_string_literal: true
-
-require 'fast_spec_helper'
-require 'rspec-parameterized'
-require_relative 'danger_spec_helper'
-
-require 'gitlab/danger/merge_request_linter'
-
-RSpec.describe Gitlab::Danger::MergeRequestLinter do
- using RSpec::Parameterized::TableSyntax
-
- let(:mr_class) do
- Struct.new(:message, :sha, :diff_parent)
- end
-
- let(:mr_title) { 'A B ' + 'C' }
- let(:merge_request) { mr_class.new(mr_title, anything, anything) }
-
- describe '#lint_subject' do
- subject(:mr_linter) { described_class.new(merge_request) }
-
- shared_examples 'a valid mr title' do
- it 'does not have any problem' do
- mr_linter.lint
-
- expect(mr_linter.problems).to be_empty
- end
- end
-
- context 'when subject valid' do
- it_behaves_like 'a valid mr title'
- end
-
- context 'when it is too long' do
- let(:mr_title) { 'A B ' + 'C' * described_class::MAX_LINE_LENGTH }
-
- it 'adds a problem' do
- expect(mr_linter).to receive(:add_problem).with(:subject_too_long, described_class.subject_description)
-
- mr_linter.lint
- end
- end
-
- describe 'using magic mr run options' do
- where(run_option: described_class.mr_run_options_regex.split('|') +
- described_class.mr_run_options_regex.split('|').map! { |x| "[#{x}]" })
-
- with_them do
- let(:mr_title) { run_option + ' A B ' + 'C' * (described_class::MAX_LINE_LENGTH - 5) }
-
- it_behaves_like 'a valid mr title'
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/danger/roulette_spec.rb b/spec/lib/gitlab/danger/roulette_spec.rb
deleted file mode 100644
index 59ac3b12b6b..00000000000
--- a/spec/lib/gitlab/danger/roulette_spec.rb
+++ /dev/null
@@ -1,413 +0,0 @@
-# frozen_string_literal: true
-
-require 'webmock/rspec'
-require 'timecop'
-
-require 'gitlab/danger/roulette'
-require 'active_support/testing/time_helpers'
-
-RSpec.describe Gitlab::Danger::Roulette do
- include ActiveSupport::Testing::TimeHelpers
-
- around do |example|
- travel_to(Time.utc(2020, 06, 22, 10)) { example.run }
- end
-
- let(:backend_available) { true }
- let(:backend_tz_offset_hours) { 2.0 }
- let(:backend_maintainer) do
- Gitlab::Danger::Teammate.new(
- 'username' => 'backend-maintainer',
- 'name' => 'Backend maintainer',
- 'role' => 'Backend engineer',
- 'projects' => { 'gitlab' => 'maintainer backend' },
- 'available' => backend_available,
- 'tz_offset_hours' => backend_tz_offset_hours
- )
- end
-
- let(:frontend_reviewer) do
- Gitlab::Danger::Teammate.new(
- 'username' => 'frontend-reviewer',
- 'name' => 'Frontend reviewer',
- 'role' => 'Frontend engineer',
- 'projects' => { 'gitlab' => 'reviewer frontend' },
- 'available' => true,
- 'tz_offset_hours' => 2.0
- )
- end
-
- let(:frontend_maintainer) do
- Gitlab::Danger::Teammate.new(
- 'username' => 'frontend-maintainer',
- 'name' => 'Frontend maintainer',
- 'role' => 'Frontend engineer',
- 'projects' => { 'gitlab' => "maintainer frontend" },
- 'available' => true,
- 'tz_offset_hours' => 2.0
- )
- end
-
- let(:software_engineer_in_test) do
- Gitlab::Danger::Teammate.new(
- 'username' => 'software-engineer-in-test',
- 'name' => 'Software Engineer in Test',
- 'role' => 'Software Engineer in Test, Create:Source Code',
- 'projects' => { 'gitlab' => 'reviewer qa', 'gitlab-qa' => 'maintainer' },
- 'available' => true,
- 'tz_offset_hours' => 2.0
- )
- end
-
- let(:engineering_productivity_reviewer) do
- Gitlab::Danger::Teammate.new(
- 'username' => 'eng-prod-reviewer',
- 'name' => 'EP engineer',
- 'role' => 'Engineering Productivity',
- 'projects' => { 'gitlab' => 'reviewer backend' },
- 'available' => true,
- 'tz_offset_hours' => 2.0
- )
- end
-
- let(:ci_template_reviewer) do
- Gitlab::Danger::Teammate.new(
- 'username' => 'ci-template-maintainer',
- 'name' => 'CI Template engineer',
- 'role' => '~"ci::templates"',
- 'projects' => { 'gitlab' => 'reviewer ci_template' },
- 'available' => true,
- 'tz_offset_hours' => 2.0
- )
- end
-
- let(:teammates) do
- [
- backend_maintainer.to_h,
- frontend_maintainer.to_h,
- frontend_reviewer.to_h,
- software_engineer_in_test.to_h,
- engineering_productivity_reviewer.to_h,
- ci_template_reviewer.to_h
- ]
- end
-
- let(:teammate_json) do
- teammates.to_json
- end
-
- subject(:roulette) { Object.new.extend(described_class) }
-
- describe 'Spin#==' do
- it 'compares Spin attributes' do
- spin1 = described_class::Spin.new(:backend, frontend_reviewer, frontend_maintainer, false, false)
- spin2 = described_class::Spin.new(:backend, frontend_reviewer, frontend_maintainer, false, false)
- spin3 = described_class::Spin.new(:backend, frontend_reviewer, frontend_maintainer, false, true)
- spin4 = described_class::Spin.new(:backend, frontend_reviewer, frontend_maintainer, true, false)
- spin5 = described_class::Spin.new(:backend, frontend_reviewer, backend_maintainer, false, false)
- spin6 = described_class::Spin.new(:backend, backend_maintainer, frontend_maintainer, false, false)
- spin7 = described_class::Spin.new(:frontend, frontend_reviewer, frontend_maintainer, false, false)
-
- expect(spin1).to eq(spin2)
- expect(spin1).not_to eq(spin3)
- expect(spin1).not_to eq(spin4)
- expect(spin1).not_to eq(spin5)
- expect(spin1).not_to eq(spin6)
- expect(spin1).not_to eq(spin7)
- end
- end
-
- describe '#spin' do
- let!(:project) { 'gitlab' }
- let!(:mr_source_branch) { 'a-branch' }
- let!(:mr_labels) { ['backend', 'devops::create'] }
- let!(:author) { Gitlab::Danger::Teammate.new('username' => 'johndoe') }
- let(:timezone_experiment) { false }
- let(:spins) do
- # Stub the request at the latest time so that we can modify the raw data, e.g. available fields.
- WebMock
- .stub_request(:get, described_class::ROULETTE_DATA_URL)
- .to_return(body: teammate_json)
-
- subject.spin(project, categories, timezone_experiment: timezone_experiment)
- end
-
- before do
- allow(subject).to receive(:mr_author_username).and_return(author.username)
- allow(subject).to receive(:mr_labels).and_return(mr_labels)
- allow(subject).to receive(:mr_source_branch).and_return(mr_source_branch)
- end
-
- context 'when timezone_experiment == false' do
- context 'when change contains backend category' do
- let(:categories) { [:backend] }
-
- it 'assigns backend reviewer and maintainer' do
- expect(spins[0].reviewer).to eq(engineering_productivity_reviewer)
- expect(spins[0].maintainer).to eq(backend_maintainer)
- expect(spins).to eq([described_class::Spin.new(:backend, engineering_productivity_reviewer, backend_maintainer, false, false)])
- end
-
- context 'when teammate is not available' do
- let(:backend_available) { false }
-
- it 'assigns backend reviewer and no maintainer' do
- expect(spins).to eq([described_class::Spin.new(:backend, engineering_productivity_reviewer, nil, false, false)])
- end
- end
- end
-
- context 'when change contains frontend category' do
- let(:categories) { [:frontend] }
-
- it 'assigns frontend reviewer and maintainer' do
- expect(spins).to eq([described_class::Spin.new(:frontend, frontend_reviewer, frontend_maintainer, false, false)])
- end
- end
-
- context 'when change contains many categories' do
- let(:categories) { [:frontend, :test, :qa, :engineering_productivity, :ci_template, :backend] }
-
- it 'has a deterministic sorting order' do
- expect(spins.map(&:category)).to eq categories.sort
- end
- end
-
- context 'when change contains QA category' do
- let(:categories) { [:qa] }
-
- it 'assigns QA reviewer' do
- expect(spins).to eq([described_class::Spin.new(:qa, software_engineer_in_test, nil, false, false)])
- end
- end
-
- context 'when change contains Engineering Productivity category' do
- let(:categories) { [:engineering_productivity] }
-
- it 'assigns Engineering Productivity reviewer and fallback to backend maintainer' do
- expect(spins).to eq([described_class::Spin.new(:engineering_productivity, engineering_productivity_reviewer, backend_maintainer, false, false)])
- end
- end
-
- context 'when change contains CI/CD Template category' do
- let(:categories) { [:ci_template] }
-
- it 'assigns CI/CD Template reviewer and fallback to backend maintainer' do
- expect(spins).to eq([described_class::Spin.new(:ci_template, ci_template_reviewer, backend_maintainer, false, false)])
- end
- end
-
- context 'when change contains test category' do
- let(:categories) { [:test] }
-
- it 'assigns corresponding SET' do
- expect(spins).to eq([described_class::Spin.new(:test, software_engineer_in_test, nil, :maintainer, false)])
- end
- end
- end
-
- context 'when timezone_experiment == true' do
- let(:timezone_experiment) { true }
-
- context 'when change contains backend category' do
- let(:categories) { [:backend] }
-
- it 'assigns backend reviewer and maintainer' do
- expect(spins).to eq([described_class::Spin.new(:backend, engineering_productivity_reviewer, backend_maintainer, false, true)])
- end
-
- context 'when teammate is not in a good timezone' do
- let(:backend_tz_offset_hours) { 5.0 }
-
- it 'assigns backend reviewer and no maintainer' do
- expect(spins).to eq([described_class::Spin.new(:backend, engineering_productivity_reviewer, nil, false, true)])
- end
- end
- end
-
- context 'when change includes a category with timezone disabled' do
- let(:categories) { [:backend] }
-
- before do
- stub_const("#{described_class}::INCLUDE_TIMEZONE_FOR_CATEGORY", backend: false)
- end
-
- it 'assigns backend reviewer and maintainer' do
- expect(spins).to eq([described_class::Spin.new(:backend, engineering_productivity_reviewer, backend_maintainer, false, false)])
- end
-
- context 'when teammate is not in a good timezone' do
- let(:backend_tz_offset_hours) { 5.0 }
-
- it 'assigns backend reviewer and maintainer' do
- expect(spins).to eq([described_class::Spin.new(:backend, engineering_productivity_reviewer, backend_maintainer, false, false)])
- end
- end
- end
- end
- end
-
- RSpec::Matchers.define :match_teammates do |expected|
- match do |actual|
- expected.each do |expected_person|
- actual_person_found = actual.find { |actual_person| actual_person.name == expected_person.username }
-
- actual_person_found &&
- actual_person_found.name == expected_person.name &&
- actual_person_found.role == expected_person.role &&
- actual_person_found.projects == expected_person.projects
- end
- end
- end
-
- describe '#team' do
- subject(:team) { roulette.team }
-
- context 'HTTP failure' do
- before do
- WebMock
- .stub_request(:get, described_class::ROULETTE_DATA_URL)
- .to_return(status: 404)
- end
-
- it 'raises a pretty error' do
- expect { team }.to raise_error(/Failed to read/)
- end
- end
-
- context 'JSON failure' do
- before do
- WebMock
- .stub_request(:get, described_class::ROULETTE_DATA_URL)
- .to_return(body: 'INVALID JSON')
- end
-
- it 'raises a pretty error' do
- expect { team }.to raise_error(/Failed to parse/)
- end
- end
-
- context 'success' do
- before do
- WebMock
- .stub_request(:get, described_class::ROULETTE_DATA_URL)
- .to_return(body: teammate_json)
- end
-
- it 'returns an array of teammates' do
- is_expected.to match_teammates([
- backend_maintainer,
- frontend_reviewer,
- frontend_maintainer,
- software_engineer_in_test,
- engineering_productivity_reviewer,
- ci_template_reviewer
- ])
- end
-
- it 'memoizes the result' do
- expect(team.object_id).to eq(roulette.team.object_id)
- end
- end
- end
-
- describe '#project_team' do
- subject { roulette.project_team('gitlab-qa') }
-
- before do
- WebMock
- .stub_request(:get, described_class::ROULETTE_DATA_URL)
- .to_return(body: teammate_json)
- end
-
- it 'filters team by project_name' do
- is_expected.to match_teammates([
- software_engineer_in_test
- ])
- end
- end
-
- describe '#spin_for_person' do
- let(:person_tz_offset_hours) { 0.0 }
- let(:person1) do
- Gitlab::Danger::Teammate.new(
- 'username' => 'user1',
- 'available' => true,
- 'tz_offset_hours' => person_tz_offset_hours
- )
- end
-
- let(:person2) do
- Gitlab::Danger::Teammate.new(
- 'username' => 'user2',
- 'available' => true,
- 'tz_offset_hours' => person_tz_offset_hours)
- end
-
- let(:author) do
- Gitlab::Danger::Teammate.new(
- 'username' => 'johndoe',
- 'available' => true,
- 'tz_offset_hours' => 0.0)
- end
-
- let(:unavailable) do
- Gitlab::Danger::Teammate.new(
- 'username' => 'janedoe',
- 'available' => false,
- 'tz_offset_hours' => 0.0)
- end
-
- before do
- allow(subject).to receive(:mr_author_username).and_return(author.username)
- end
-
- (-4..4).each do |utc_offset|
- context "when local hour for person is #{10 + utc_offset} (offset: #{utc_offset})" do
- let(:person_tz_offset_hours) { utc_offset }
-
- [false, true].each do |timezone_experiment|
- context "with timezone_experiment == #{timezone_experiment}" do
- it 'returns a random person' do
- persons = [person1, person2]
-
- selected = subject.spin_for_person(persons, random: Random.new, timezone_experiment: timezone_experiment)
-
- expect(persons.map(&:username)).to include(selected.username)
- end
- end
- end
- end
- end
-
- ((-12..-5).to_a + (5..12).to_a).each do |utc_offset|
- context "when local hour for person is #{10 + utc_offset} (offset: #{utc_offset})" do
- let(:person_tz_offset_hours) { utc_offset }
-
- [false, true].each do |timezone_experiment|
- context "with timezone_experiment == #{timezone_experiment}" do
- it 'returns a random person or nil' do
- persons = [person1, person2]
-
- selected = subject.spin_for_person(persons, random: Random.new, timezone_experiment: timezone_experiment)
-
- if timezone_experiment
- expect(selected).to be_nil
- else
- expect(persons.map(&:username)).to include(selected.username)
- end
- end
- end
- end
- end
- end
-
- it 'excludes unavailable persons' do
- expect(subject.spin_for_person([unavailable], random: Random.new)).to be_nil
- end
-
- it 'excludes mr.author' do
- expect(subject.spin_for_person([author], random: Random.new)).to be_nil
- end
- end
-end
diff --git a/spec/lib/gitlab/danger/sidekiq_queues_spec.rb b/spec/lib/gitlab/danger/sidekiq_queues_spec.rb
deleted file mode 100644
index 7dd1a2e6924..00000000000
--- a/spec/lib/gitlab/danger/sidekiq_queues_spec.rb
+++ /dev/null
@@ -1,82 +0,0 @@
-# frozen_string_literal: true
-
-require 'fast_spec_helper'
-require 'rspec-parameterized'
-require_relative 'danger_spec_helper'
-
-require 'gitlab/danger/sidekiq_queues'
-
-RSpec.describe Gitlab::Danger::SidekiqQueues do
- using RSpec::Parameterized::TableSyntax
- include DangerSpecHelper
-
- let(:fake_git) { double('fake-git') }
- let(:fake_danger) { new_fake_danger.include(described_class) }
-
- subject(:sidekiq_queues) { fake_danger.new(git: fake_git) }
-
- describe '#changed_queue_files' do
- where(:modified_files, :changed_queue_files) do
- %w(app/workers/all_queues.yml ee/app/workers/all_queues.yml foo) | %w(app/workers/all_queues.yml ee/app/workers/all_queues.yml)
- %w(app/workers/all_queues.yml ee/app/workers/all_queues.yml) | %w(app/workers/all_queues.yml ee/app/workers/all_queues.yml)
- %w(app/workers/all_queues.yml foo) | %w(app/workers/all_queues.yml)
- %w(ee/app/workers/all_queues.yml foo) | %w(ee/app/workers/all_queues.yml)
- %w(foo) | %w()
- %w() | %w()
- end
-
- with_them do
- it do
- allow(fake_git).to receive(:modified_files).and_return(modified_files)
-
- expect(sidekiq_queues.changed_queue_files).to match_array(changed_queue_files)
- end
- end
- end
-
- describe '#added_queue_names' do
- it 'returns queue names added by this change' do
- old_queues = { post_receive: nil }
-
- allow(sidekiq_queues).to receive(:old_queues).and_return(old_queues)
- allow(sidekiq_queues).to receive(:new_queues).and_return(old_queues.merge(merge: nil, process_commit: nil))
-
- expect(sidekiq_queues.added_queue_names).to contain_exactly(:merge, :process_commit)
- end
- end
-
- describe '#changed_queue_names' do
- it 'returns names for queues whose attributes were changed' do
- old_queues = {
- merge: { name: :merge, urgency: :low },
- post_receive: { name: :post_receive, urgency: :high },
- process_commit: { name: :process_commit, urgency: :high }
- }
-
- new_queues = old_queues.merge(mailers: { name: :mailers, urgency: :high },
- post_receive: { name: :post_receive, urgency: :low },
- process_commit: { name: :process_commit, urgency: :low })
-
- allow(sidekiq_queues).to receive(:old_queues).and_return(old_queues)
- allow(sidekiq_queues).to receive(:new_queues).and_return(new_queues)
-
- expect(sidekiq_queues.changed_queue_names).to contain_exactly(:post_receive, :process_commit)
- end
-
- it 'ignores removed queues' do
- old_queues = {
- merge: { name: :merge, urgency: :low },
- post_receive: { name: :post_receive, urgency: :high }
- }
-
- new_queues = {
- post_receive: { name: :post_receive, urgency: :low }
- }
-
- allow(sidekiq_queues).to receive(:old_queues).and_return(old_queues)
- allow(sidekiq_queues).to receive(:new_queues).and_return(new_queues)
-
- expect(sidekiq_queues.changed_queue_names).to contain_exactly(:post_receive)
- end
- end
-end
diff --git a/spec/lib/gitlab/danger/teammate_spec.rb b/spec/lib/gitlab/danger/teammate_spec.rb
deleted file mode 100644
index 9c066ba4c1b..00000000000
--- a/spec/lib/gitlab/danger/teammate_spec.rb
+++ /dev/null
@@ -1,220 +0,0 @@
-# frozen_string_literal: true
-
-require 'timecop'
-require 'rspec-parameterized'
-
-require 'gitlab/danger/teammate'
-require 'active_support/testing/time_helpers'
-
-RSpec.describe Gitlab::Danger::Teammate do
- using RSpec::Parameterized::TableSyntax
-
- subject { described_class.new(options) }
-
- let(:tz_offset_hours) { 2.0 }
- let(:options) do
- {
- 'username' => 'luigi',
- 'projects' => projects,
- 'role' => role,
- 'markdown_name' => '[Luigi](https://gitlab.com/luigi) (`@luigi`)',
- 'tz_offset_hours' => tz_offset_hours
- }
- end
-
- let(:capabilities) { ['reviewer backend'] }
- let(:projects) { { project => capabilities } }
- let(:role) { 'Engineer, Manage' }
- let(:labels) { [] }
- let(:project) { double }
-
- describe '#==' do
- it 'compares Teammate username' do
- joe1 = described_class.new('username' => 'joe', 'projects' => projects)
- joe2 = described_class.new('username' => 'joe', 'projects' => [])
- jane1 = described_class.new('username' => 'jane', 'projects' => projects)
- jane2 = described_class.new('username' => 'jane', 'projects' => [])
-
- expect(joe1).to eq(joe2)
- expect(jane1).to eq(jane2)
- expect(jane1).not_to eq(nil)
- expect(described_class.new('username' => nil)).not_to eq(nil)
- end
- end
-
- describe '#to_h' do
- it 'returns the given options' do
- expect(subject.to_h).to eq(options)
- end
- end
-
- context 'when having multiple capabilities' do
- let(:capabilities) { ['reviewer backend', 'maintainer frontend', 'trainee_maintainer qa'] }
-
- it '#reviewer? supports multiple roles per project' do
- expect(subject.reviewer?(project, :backend, labels)).to be_truthy
- end
-
- it '#traintainer? supports multiple roles per project' do
- expect(subject.traintainer?(project, :qa, labels)).to be_truthy
- end
-
- it '#maintainer? supports multiple roles per project' do
- expect(subject.maintainer?(project, :frontend, labels)).to be_truthy
- end
-
- context 'when labels contain devops::create and the category is test' do
- let(:labels) { ['devops::create'] }
-
- context 'when role is Software Engineer in Test, Create' do
- let(:role) { 'Software Engineer in Test, Create' }
-
- it '#reviewer? returns true' do
- expect(subject.reviewer?(project, :test, labels)).to be_truthy
- end
-
- it '#maintainer? returns false' do
- expect(subject.maintainer?(project, :test, labels)).to be_falsey
- end
-
- context 'when hyperlink is mangled in the role' do
- let(:role) { '<a href="#">Software Engineer in Test</a>, Create' }
-
- it '#reviewer? returns true' do
- expect(subject.reviewer?(project, :test, labels)).to be_truthy
- end
- end
- end
-
- context 'when role is Software Engineer in Test' do
- let(:role) { 'Software Engineer in Test' }
-
- it '#reviewer? returns false' do
- expect(subject.reviewer?(project, :test, labels)).to be_falsey
- end
- end
-
- context 'when role is Software Engineer in Test, Manage' do
- let(:role) { 'Software Engineer in Test, Manage' }
-
- it '#reviewer? returns false' do
- expect(subject.reviewer?(project, :test, labels)).to be_falsey
- end
- end
-
- context 'when role is Backend Engineer, Engineering Productivity' do
- let(:role) { 'Backend Engineer, Engineering Productivity' }
-
- it '#reviewer? returns true' do
- expect(subject.reviewer?(project, :engineering_productivity, labels)).to be_truthy
- end
-
- it '#maintainer? returns false' do
- expect(subject.maintainer?(project, :engineering_productivity, labels)).to be_falsey
- end
-
- context 'when capabilities include maintainer backend' do
- let(:capabilities) { ['maintainer backend'] }
-
- it '#maintainer? returns true' do
- expect(subject.maintainer?(project, :engineering_productivity, labels)).to be_truthy
- 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'] }
-
- it '#traintainer? returns true' do
- expect(subject.traintainer?(project, :engineering_productivity, labels)).to be_truthy
- end
- end
- end
- end
- end
-
- context 'when having single capability' do
- let(:capabilities) { 'reviewer backend' }
-
- it '#reviewer? supports one role per project' do
- expect(subject.reviewer?(project, :backend, labels)).to be_truthy
- end
-
- it '#traintainer? supports one role per project' do
- expect(subject.traintainer?(project, :database, labels)).to be_falsey
- end
-
- it '#maintainer? supports one role per project' do
- expect(subject.maintainer?(project, :frontend, labels)).to be_falsey
- end
- end
-
- describe '#local_hour' do
- include ActiveSupport::Testing::TimeHelpers
-
- around do |example|
- travel_to(Time.utc(2020, 6, 23, 10)) { example.run }
- end
-
- context 'when author is given' do
- where(:tz_offset_hours, :expected_local_hour) do
- -12 | 22
- -10 | 0
- 2 | 12
- 4 | 14
- 12 | 22
- end
-
- with_them do
- it 'returns the correct local_hour' do
- expect(subject.local_hour).to eq(expected_local_hour)
- end
- end
- end
- end
-
- describe '#markdown_name' do
- it 'returns markdown name with timezone info' do
- expect(subject.markdown_name).to eq("#{options['markdown_name']} (UTC+2)")
- end
-
- context 'when offset is 1.5' do
- let(:tz_offset_hours) { 1.5 }
-
- it 'returns markdown name with timezone info, not truncated' do
- expect(subject.markdown_name).to eq("#{options['markdown_name']} (UTC+1.5)")
- end
- end
-
- context 'when author is given' do
- where(:tz_offset_hours, :author_offset, :diff_text) do
- -12 | -10 | "2 hours behind `@mario`"
- -10 | -12 | "2 hours ahead of `@mario`"
- -10 | 2 | "12 hours behind `@mario`"
- 2 | 4 | "2 hours behind `@mario`"
- 4 | 2 | "2 hours ahead of `@mario`"
- 2 | 3 | "1 hour behind `@mario`"
- 3 | 2 | "1 hour ahead of `@mario`"
- 2 | 2 | "same timezone as `@mario`"
- end
-
- with_them do
- it 'returns markdown name with timezone info' do
- author = described_class.new(options.merge('username' => 'mario', 'tz_offset_hours' => author_offset))
-
- floored_offset_hours = subject.__send__(:floored_offset_hours)
- utc_offset = floored_offset_hours >= 0 ? "+#{floored_offset_hours}" : floored_offset_hours
-
- expect(subject.markdown_name(author: author)).to eq("#{options['markdown_name']} (UTC#{utc_offset}, #{diff_text})")
- end
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/danger/title_linting_spec.rb b/spec/lib/gitlab/danger/title_linting_spec.rb
deleted file mode 100644
index b48d2c5e53d..00000000000
--- a/spec/lib/gitlab/danger/title_linting_spec.rb
+++ /dev/null
@@ -1,56 +0,0 @@
-# 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
deleted file mode 100644
index 066bb487fa2..00000000000
--- a/spec/lib/gitlab/danger/weightage/maintainers_spec.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-# 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
deleted file mode 100644
index cca81f4d9b5..00000000000
--- a/spec/lib/gitlab/danger/weightage/reviewers_spec.rb
+++ /dev/null
@@ -1,63 +0,0 @@
-# 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 2f74e766a11..4242469b3db 100644
--- a/spec/lib/gitlab/data_builder/build_spec.rb
+++ b/spec/lib/gitlab/data_builder/build_spec.rb
@@ -3,7 +3,8 @@
require 'spec_helper'
RSpec.describe Gitlab::DataBuilder::Build do
- let(:runner) { create(:ci_runner, :instance) }
+ let!(:tag_names) { %w(tag-1 tag-2) }
+ let(:runner) { create(:ci_runner, :instance, tag_list: tag_names.map { |n| ActsAsTaggableOn::Tag.create!(name: n)}) }
let(:user) { create(:user) }
let(:build) { create(:ci_build, :running, runner: runner, user: user) }
@@ -35,6 +36,7 @@ RSpec.describe Gitlab::DataBuilder::Build do
}
it { expect(data[:commit][:id]).to eq(build.pipeline.id) }
it { expect(data[:runner][:id]).to eq(build.runner.id) }
+ it { expect(data[:runner][:tags]).to match_array(tag_names) }
it { expect(data[:runner][:description]).to eq(build.runner.description) }
context 'commit author_url' do
diff --git a/spec/lib/gitlab/data_builder/pipeline_spec.rb b/spec/lib/gitlab/data_builder/pipeline_spec.rb
index 297d87708d8..32619fc4c37 100644
--- a/spec/lib/gitlab/data_builder/pipeline_spec.rb
+++ b/spec/lib/gitlab/data_builder/pipeline_spec.rb
@@ -51,13 +51,15 @@ RSpec.describe Gitlab::DataBuilder::Pipeline do
context 'build with runner' do
let!(:build) { create(:ci_build, pipeline: pipeline, runner: ci_runner) }
- let(:ci_runner) { create(:ci_runner) }
+ let!(:tag_names) { %w(tag-1 tag-2) }
+ let(:ci_runner) { create(:ci_runner, tag_list: tag_names.map { |n| ActsAsTaggableOn::Tag.create!(name: n)}) }
it 'has runner attributes', :aggregate_failures do
expect(runner_data[:id]).to eq(ci_runner.id)
expect(runner_data[:description]).to eq(ci_runner.description)
expect(runner_data[:active]).to eq(ci_runner.active)
expect(runner_data[:is_shared]).to eq(ci_runner.instance_type?)
+ expect(runner_data[:tags]).to match_array(tag_names)
end
end
diff --git a/spec/lib/gitlab/database/migration_helpers/v2_spec.rb b/spec/lib/gitlab/database/migration_helpers/v2_spec.rb
new file mode 100644
index 00000000000..f132ecbf13b
--- /dev/null
+++ b/spec/lib/gitlab/database/migration_helpers/v2_spec.rb
@@ -0,0 +1,221 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::MigrationHelpers::V2 do
+ include Database::TriggerHelpers
+
+ let(:migration) do
+ ActiveRecord::Migration.new.extend(described_class)
+ end
+
+ before do
+ allow(migration).to receive(:puts)
+ end
+
+ shared_examples_for 'Setting up to rename a column' do
+ let(:model) { Class.new(ActiveRecord::Base) }
+
+ before do
+ model.table_name = :test_table
+ end
+
+ context 'when called inside a transaction block' do
+ before do
+ allow(migration).to receive(:transaction_open?).and_return(true)
+ end
+
+ it 'raises an error' do
+ expect do
+ migration.public_send(operation, :test_table, :original, :renamed)
+ end.to raise_error("#{operation} can not be run inside a transaction")
+ end
+ end
+
+ context 'when the existing column has a default value' do
+ before do
+ migration.change_column_default :test_table, existing_column, 'default value'
+ end
+
+ it 'raises an error' do
+ expect do
+ migration.public_send(operation, :test_table, :original, :renamed)
+ end.to raise_error("#{operation} does not currently support columns with default values")
+ end
+ end
+
+ context 'when passing a batch column' do
+ context 'when the batch column does not exist' do
+ it 'raises an error' do
+ expect do
+ migration.public_send(operation, :test_table, :original, :renamed, batch_column_name: :missing)
+ end.to raise_error('Column missing does not exist on test_table')
+ end
+ end
+
+ context 'when the batch column does exist' do
+ it 'passes it when creating the column' do
+ expect(migration).to receive(:create_column_from)
+ .with(:test_table, existing_column, added_column, type: nil, batch_column_name: :status)
+ .and_call_original
+
+ migration.public_send(operation, :test_table, :original, :renamed, batch_column_name: :status)
+ end
+ end
+ end
+
+ it 'creates the renamed column, syncing existing data' do
+ existing_record_1 = model.create!(status: 0, existing_column => 'existing')
+ existing_record_2 = model.create!(status: 0, existing_column => nil)
+
+ migration.send(operation, :test_table, :original, :renamed)
+ model.reset_column_information
+
+ expect(migration.column_exists?(:test_table, added_column)).to eq(true)
+
+ expect(existing_record_1.reload).to have_attributes(status: 0, original: 'existing', renamed: 'existing')
+ expect(existing_record_2.reload).to have_attributes(status: 0, original: nil, renamed: nil)
+ end
+
+ it 'installs triggers to sync new data' do
+ migration.public_send(operation, :test_table, :original, :renamed)
+ model.reset_column_information
+
+ new_record_1 = model.create!(status: 1, original: 'first')
+ new_record_2 = model.create!(status: 1, renamed: 'second')
+
+ expect(new_record_1.reload).to have_attributes(status: 1, original: 'first', renamed: 'first')
+ expect(new_record_2.reload).to have_attributes(status: 1, original: 'second', renamed: 'second')
+
+ new_record_1.update!(original: 'updated')
+ new_record_2.update!(renamed: nil)
+
+ expect(new_record_1.reload).to have_attributes(status: 1, original: 'updated', renamed: 'updated')
+ expect(new_record_2.reload).to have_attributes(status: 1, original: nil, renamed: nil)
+ end
+ end
+
+ describe '#rename_column_concurrently' do
+ before do
+ allow(migration).to receive(:transaction_open?).and_return(false)
+
+ migration.create_table :test_table do |t|
+ t.integer :status, null: false
+ t.text :original
+ t.text :other_column
+ end
+ end
+
+ it_behaves_like 'Setting up to rename a column' do
+ let(:operation) { :rename_column_concurrently }
+ let(:existing_column) { :original }
+ let(:added_column) { :renamed }
+ end
+
+ context 'when the column to rename does not exist' do
+ it 'raises an error' do
+ expect do
+ migration.rename_column_concurrently :test_table, :missing_column, :renamed
+ end.to raise_error('Column missing_column does not exist on test_table')
+ end
+ end
+ end
+
+ describe '#undo_cleanup_concurrent_column_rename' do
+ before do
+ allow(migration).to receive(:transaction_open?).and_return(false)
+
+ migration.create_table :test_table do |t|
+ t.integer :status, null: false
+ t.text :other_column
+ t.text :renamed
+ end
+ end
+
+ it_behaves_like 'Setting up to rename a column' do
+ let(:operation) { :undo_cleanup_concurrent_column_rename }
+ let(:existing_column) { :renamed }
+ let(:added_column) { :original }
+ end
+
+ context 'when the renamed column does not exist' do
+ it 'raises an error' do
+ expect do
+ migration.undo_cleanup_concurrent_column_rename :test_table, :original, :missing_column
+ end.to raise_error('Column missing_column does not exist on test_table')
+ end
+ end
+ end
+
+ shared_examples_for 'Cleaning up from renaming a column' do
+ let(:connection) { migration.connection }
+
+ before do
+ allow(migration).to receive(:transaction_open?).and_return(false)
+
+ migration.create_table :test_table do |t|
+ t.integer :status, null: false
+ t.text :original
+ t.text :other_column
+ end
+
+ migration.rename_column_concurrently :test_table, :original, :renamed
+ end
+
+ context 'when the helper is called repeatedly' do
+ before do
+ migration.public_send(operation, :test_table, :original, :renamed)
+ end
+
+ it 'does not make repeated attempts to cleanup' do
+ expect(migration).not_to receive(:remove_column)
+
+ expect do
+ migration.public_send(operation, :test_table, :original, :renamed)
+ end.not_to raise_error
+ end
+ end
+
+ context 'when the renamed column exists' do
+ let(:triggers) do
+ [
+ ['trigger_7cc71f92fd63', 'function_for_trigger_7cc71f92fd63', before: 'insert'],
+ ['trigger_f1a1f619636a', 'function_for_trigger_f1a1f619636a', before: 'update'],
+ ['trigger_769a49938884', 'function_for_trigger_769a49938884', before: 'update']
+ ]
+ end
+
+ it 'removes the sync triggers and renamed columns' do
+ triggers.each do |(trigger_name, function_name, event)|
+ expect_function_to_exist(function_name)
+ expect_valid_function_trigger(:test_table, trigger_name, function_name, event)
+ end
+
+ expect(migration.column_exists?(:test_table, added_column)).to eq(true)
+
+ migration.public_send(operation, :test_table, :original, :renamed)
+
+ expect(migration.column_exists?(:test_table, added_column)).to eq(false)
+
+ triggers.each do |(trigger_name, function_name, _)|
+ expect_trigger_not_to_exist(:test_table, trigger_name)
+ expect_function_not_to_exist(function_name)
+ end
+ end
+ end
+ end
+
+ describe '#undo_rename_column_concurrently' do
+ it_behaves_like 'Cleaning up from renaming a column' do
+ let(:operation) { :undo_rename_column_concurrently }
+ let(:added_column) { :renamed }
+ end
+ end
+
+ describe '#cleanup_concurrent_column_rename' do
+ it_behaves_like 'Cleaning up from renaming a column' do
+ let(:operation) { :cleanup_concurrent_column_rename }
+ let(:added_column) { :original }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index 6b709cba5b3..6de7fc3a50e 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -1874,7 +1874,6 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
has_internal_id :iid,
scope: :project,
init: ->(s, _scope) { s&.project&.issues&.maximum(:iid) },
- backfill: true,
presence: false
end
end
@@ -1928,258 +1927,6 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
expect(issue_b.iid).to eq(3)
end
- context 'when the new code creates a row post deploy but before the migration runs' do
- it 'does not change the row iid' do
- project = setup
- issue = Issue.create!(project_id: project.id)
-
- model.backfill_iids('issues')
-
- expect(issue.reload.iid).to eq(1)
- end
-
- it 'backfills iids for rows already in the database' do
- project = setup
- issue_a = issues.create!(project_id: project.id)
- issue_b = issues.create!(project_id: project.id)
- issue_c = Issue.create!(project_id: project.id)
-
- model.backfill_iids('issues')
-
- expect(issue_a.reload.iid).to eq(1)
- expect(issue_b.reload.iid).to eq(2)
- expect(issue_c.reload.iid).to eq(3)
- end
-
- it 'backfills iids across multiple projects' do
- project_a = setup
- project_b = setup
- issue_a = issues.create!(project_id: project_a.id)
- issue_b = issues.create!(project_id: project_b.id)
- issue_c = Issue.create!(project_id: project_a.id)
- issue_d = Issue.create!(project_id: project_b.id)
-
- model.backfill_iids('issues')
-
- expect(issue_a.reload.iid).to eq(1)
- expect(issue_b.reload.iid).to eq(1)
- expect(issue_c.reload.iid).to eq(2)
- expect(issue_d.reload.iid).to eq(2)
- end
-
- it 'generates iids properly for models created after the migration' do
- project = setup
- issue_a = issues.create!(project_id: project.id)
- issue_b = issues.create!(project_id: project.id)
- issue_c = Issue.create!(project_id: project.id)
-
- model.backfill_iids('issues')
-
- issue_d = Issue.create!(project_id: project.id)
- issue_e = Issue.create!(project_id: project.id)
-
- expect(issue_a.reload.iid).to eq(1)
- expect(issue_b.reload.iid).to eq(2)
- expect(issue_c.reload.iid).to eq(3)
- expect(issue_d.iid).to eq(4)
- expect(issue_e.iid).to eq(5)
- end
-
- it 'backfills iids and properly generates iids for new models across multiple projects' do
- project_a = setup
- project_b = setup
- issue_a = issues.create!(project_id: project_a.id)
- issue_b = issues.create!(project_id: project_b.id)
- issue_c = Issue.create!(project_id: project_a.id)
- issue_d = Issue.create!(project_id: project_b.id)
-
- model.backfill_iids('issues')
-
- issue_e = Issue.create!(project_id: project_a.id)
- issue_f = Issue.create!(project_id: project_b.id)
- issue_g = Issue.create!(project_id: project_a.id)
-
- expect(issue_a.reload.iid).to eq(1)
- expect(issue_b.reload.iid).to eq(1)
- expect(issue_c.reload.iid).to eq(2)
- expect(issue_d.reload.iid).to eq(2)
- expect(issue_e.iid).to eq(3)
- expect(issue_f.iid).to eq(3)
- expect(issue_g.iid).to eq(4)
- end
- end
-
- context 'when the new code creates a model and then old code creates a model post deploy but before the migration runs' do
- it 'backfills iids' do
- project = setup
- issue_a = issues.create!(project_id: project.id)
- issue_b = Issue.create!(project_id: project.id)
- issue_c = issues.create!(project_id: project.id)
-
- model.backfill_iids('issues')
-
- expect(issue_a.reload.iid).to eq(1)
- expect(issue_b.reload.iid).to eq(2)
- expect(issue_c.reload.iid).to eq(3)
- end
-
- it 'generates an iid for a new model after the migration' do
- project = setup
- issue_a = issues.create!(project_id: project.id)
- issue_b = issues.create!(project_id: project.id)
- issue_c = Issue.create!(project_id: project.id)
- issue_d = issues.create!(project_id: project.id)
-
- model.backfill_iids('issues')
-
- issue_e = Issue.create!(project_id: project.id)
-
- expect(issue_a.reload.iid).to eq(1)
- expect(issue_b.reload.iid).to eq(2)
- expect(issue_c.reload.iid).to eq(3)
- expect(issue_d.reload.iid).to eq(4)
- expect(issue_e.iid).to eq(5)
- end
- end
-
- context 'when the new code and old code alternate creating models post deploy but before the migration runs' do
- it 'backfills iids' do
- project = setup
- issue_a = issues.create!(project_id: project.id)
- issue_b = Issue.create!(project_id: project.id)
- issue_c = issues.create!(project_id: project.id)
- issue_d = Issue.create!(project_id: project.id)
-
- model.backfill_iids('issues')
-
- expect(issue_a.reload.iid).to eq(1)
- expect(issue_b.reload.iid).to eq(2)
- expect(issue_c.reload.iid).to eq(3)
- expect(issue_d.reload.iid).to eq(4)
- end
-
- it 'generates an iid for a new model after the migration' do
- project = setup
- issue_a = issues.create!(project_id: project.id)
- issue_b = issues.create!(project_id: project.id)
- issue_c = Issue.create!(project_id: project.id)
- issue_d = issues.create!(project_id: project.id)
- issue_e = Issue.create!(project_id: project.id)
-
- model.backfill_iids('issues')
-
- issue_f = Issue.create!(project_id: project.id)
-
- expect(issue_a.reload.iid).to eq(1)
- expect(issue_b.reload.iid).to eq(2)
- expect(issue_c.reload.iid).to eq(3)
- expect(issue_d.reload.iid).to eq(4)
- expect(issue_e.reload.iid).to eq(5)
- expect(issue_f.iid).to eq(6)
- end
- end
-
- context 'when the new code creates and deletes a model post deploy but before the migration runs' do
- it 'backfills iids for rows already in the database' do
- project = setup
- issue_a = issues.create!(project_id: project.id)
- issue_b = issues.create!(project_id: project.id)
- issue_c = Issue.create!(project_id: project.id)
- issue_c.delete
-
- model.backfill_iids('issues')
-
- expect(issue_a.reload.iid).to eq(1)
- expect(issue_b.reload.iid).to eq(2)
- end
-
- it 'successfully creates a new model after the migration' do
- project = setup
- issue_a = issues.create!(project_id: project.id)
- issue_b = issues.create!(project_id: project.id)
- issue_c = Issue.create!(project_id: project.id)
- issue_c.delete
-
- model.backfill_iids('issues')
-
- issue_d = Issue.create!(project_id: project.id)
-
- expect(issue_a.reload.iid).to eq(1)
- expect(issue_b.reload.iid).to eq(2)
- expect(issue_d.iid).to eq(3)
- end
- end
-
- context 'when the new code creates and deletes a model and old code creates a model post deploy but before the migration runs' do
- it 'backfills iids' do
- project = setup
- issue_a = issues.create!(project_id: project.id)
- issue_b = issues.create!(project_id: project.id)
- issue_c = Issue.create!(project_id: project.id)
- issue_c.delete
- issue_d = issues.create!(project_id: project.id)
-
- model.backfill_iids('issues')
-
- expect(issue_a.reload.iid).to eq(1)
- expect(issue_b.reload.iid).to eq(2)
- expect(issue_d.reload.iid).to eq(3)
- end
-
- it 'successfully creates a new model after the migration' do
- project = setup
- issue_a = issues.create!(project_id: project.id)
- issue_b = issues.create!(project_id: project.id)
- issue_c = Issue.create!(project_id: project.id)
- issue_c.delete
- issue_d = issues.create!(project_id: project.id)
-
- model.backfill_iids('issues')
-
- issue_e = Issue.create!(project_id: project.id)
-
- expect(issue_a.reload.iid).to eq(1)
- expect(issue_b.reload.iid).to eq(2)
- expect(issue_d.reload.iid).to eq(3)
- expect(issue_e.iid).to eq(4)
- end
- end
-
- context 'when the new code creates and deletes a model and then creates another model post deploy but before the migration runs' do
- it 'successfully generates an iid for a new model after the migration' do
- project = setup
- issue_a = issues.create!(project_id: project.id)
- issue_b = issues.create!(project_id: project.id)
- issue_c = Issue.create!(project_id: project.id)
- issue_c.delete
- issue_d = Issue.create!(project_id: project.id)
-
- model.backfill_iids('issues')
-
- expect(issue_a.reload.iid).to eq(1)
- expect(issue_b.reload.iid).to eq(2)
- expect(issue_d.reload.iid).to eq(3)
- end
-
- it 'successfully generates an iid for a new model after the migration' do
- project = setup
- issue_a = issues.create!(project_id: project.id)
- issue_b = issues.create!(project_id: project.id)
- issue_c = Issue.create!(project_id: project.id)
- issue_c.delete
- issue_d = Issue.create!(project_id: project.id)
-
- model.backfill_iids('issues')
-
- issue_e = Issue.create!(project_id: project.id)
-
- expect(issue_a.reload.iid).to eq(1)
- expect(issue_b.reload.iid).to eq(2)
- expect(issue_d.reload.iid).to eq(3)
- expect(issue_e.iid).to eq(4)
- end
- end
-
context 'when the first model is created for a project after the migration' do
it 'generates an iid' do
project_a = setup
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 b50e02c7043..b5d741fc5e9 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
@@ -513,6 +513,7 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHe
context 'finishing pending background migration jobs' do
let(:source_table_double) { double('table name') }
let(:raw_arguments) { [1, 50_000, source_table_double, partitioned_table, source_column] }
+ let(:background_job) { double('background job', args: ['background jobs', raw_arguments]) }
before do
allow(migration).to receive(:table_exists?).with(partitioned_table).and_return(true)
@@ -528,7 +529,7 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHe
expect(Gitlab::BackgroundMigration).to receive(:steal)
.with(described_class::MIGRATION_CLASS_NAME)
- .and_yield(raw_arguments)
+ .and_yield(background_job)
expect(source_table_double).to receive(:==).with(source_table.to_s)
diff --git a/spec/lib/gitlab/database/with_lock_retries_spec.rb b/spec/lib/gitlab/database/with_lock_retries_spec.rb
index 220ae705e71..563399ff0d9 100644
--- a/spec/lib/gitlab/database/with_lock_retries_spec.rb
+++ b/spec/lib/gitlab/database/with_lock_retries_spec.rb
@@ -54,6 +54,10 @@ RSpec.describe Gitlab::Database::WithLockRetries do
lock_fiber.resume # start the transaction and lock the table
end
+ after do
+ lock_fiber.resume if lock_fiber.alive?
+ end
+
context 'lock_fiber' do
it 'acquires lock successfully' do
check_exclusive_lock_query = """
diff --git a/spec/lib/gitlab/diff/char_diff_spec.rb b/spec/lib/gitlab/diff/char_diff_spec.rb
new file mode 100644
index 00000000000..e4e2a3ba050
--- /dev/null
+++ b/spec/lib/gitlab/diff/char_diff_spec.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require 'diff_match_patch'
+
+RSpec.describe Gitlab::Diff::CharDiff do
+ let(:old_string) { "Helo \n Worlld" }
+ let(:new_string) { "Hello \n World" }
+
+ subject(:diff) { described_class.new(old_string, new_string) }
+
+ describe '#generate_diff' do
+ context 'when old string is nil' do
+ let(:old_string) { nil }
+
+ it 'does not raise an error' do
+ expect { subject.generate_diff }.not_to raise_error
+ end
+
+ it 'treats nil values as blank strings' do
+ changes = subject.generate_diff
+
+ expect(changes).to eq([
+ [:insert, "Hello \n World"]
+ ])
+ end
+ end
+
+ it 'generates an array of changes' do
+ changes = subject.generate_diff
+
+ expect(changes).to eq([
+ [:equal, "Hel"],
+ [:insert, "l"],
+ [:equal, "o \n Worl"],
+ [:delete, "l"],
+ [:equal, "d"]
+ ])
+ end
+ end
+
+ describe '#changed_ranges' do
+ subject { diff.changed_ranges }
+
+ context 'when old string is nil' do
+ let(:old_string) { nil }
+
+ it 'returns lists of changes' do
+ old_diffs, new_diffs = subject
+
+ expect(old_diffs).to eq([])
+ expect(new_diffs).to eq([0..12])
+ end
+ end
+
+ it 'returns ranges of changes' do
+ old_diffs, new_diffs = subject
+
+ expect(old_diffs).to eq([11..11])
+ expect(new_diffs).to eq([3..3])
+ end
+ end
+
+ describe '#to_html' do
+ it 'returns an HTML representation of the diff' do
+ subject.generate_diff
+
+ expect(subject.to_html).to eq(
+ '<span class="idiff">Hel</span>' \
+ '<span class="idiff addition">l</span>' \
+ "<span class=\"idiff\">o \n Worl</span>" \
+ '<span class="idiff deletion">l</span>' \
+ '<span class="idiff">d</span>'
+ )
+ end
+ end
+end
diff --git a/spec/lib/gitlab/diff/file_collection_sorter_spec.rb b/spec/lib/gitlab/diff/file_collection_sorter_spec.rb
index 8822fc55c6e..9ba9271cefc 100644
--- a/spec/lib/gitlab/diff/file_collection_sorter_spec.rb
+++ b/spec/lib/gitlab/diff/file_collection_sorter_spec.rb
@@ -5,11 +5,14 @@ require 'spec_helper'
RSpec.describe Gitlab::Diff::FileCollectionSorter do
let(:diffs) do
[
+ double(new_path: 'README', old_path: 'README'),
double(new_path: '.dir/test', old_path: '.dir/test'),
double(new_path: '', old_path: '.file'),
double(new_path: '1-folder/A-file.ext', old_path: '1-folder/A-file.ext'),
+ double(new_path: '1-folder/README', old_path: '1-folder/README'),
double(new_path: nil, old_path: '1-folder/M-file.ext'),
double(new_path: '1-folder/Z-file.ext', old_path: '1-folder/Z-file.ext'),
+ double(new_path: '1-folder/README', old_path: '1-folder/README'),
double(new_path: '', old_path: '1-folder/nested/A-file.ext'),
double(new_path: '1-folder/nested/M-file.ext', old_path: '1-folder/nested/M-file.ext'),
double(new_path: nil, old_path: '1-folder/nested/Z-file.ext'),
@@ -19,7 +22,8 @@ RSpec.describe Gitlab::Diff::FileCollectionSorter do
double(new_path: nil, old_path: '2-folder/nested/A-file.ext'),
double(new_path: 'A-file.ext', old_path: 'A-file.ext'),
double(new_path: '', old_path: 'M-file.ext'),
- double(new_path: 'Z-file.ext', old_path: 'Z-file.ext')
+ double(new_path: 'Z-file.ext', old_path: 'Z-file.ext'),
+ double(new_path: 'README', old_path: 'README')
]
end
@@ -36,6 +40,8 @@ RSpec.describe Gitlab::Diff::FileCollectionSorter do
'1-folder/nested/Z-file.ext',
'1-folder/A-file.ext',
'1-folder/M-file.ext',
+ '1-folder/README',
+ '1-folder/README',
'1-folder/Z-file.ext',
'2-folder/nested/A-file.ext',
'2-folder/A-file.ext',
@@ -44,6 +50,8 @@ RSpec.describe Gitlab::Diff::FileCollectionSorter do
'.file',
'A-file.ext',
'M-file.ext',
+ 'README',
+ 'README',
'Z-file.ext'
])
end
diff --git a/spec/lib/gitlab/diff/highlight_cache_spec.rb b/spec/lib/gitlab/diff/highlight_cache_spec.rb
index f6810d7a966..94717152488 100644
--- a/spec/lib/gitlab/diff/highlight_cache_spec.rb
+++ b/spec/lib/gitlab/diff/highlight_cache_spec.rb
@@ -233,4 +233,22 @@ RSpec.describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache do
cache.write_if_empty
end
end
+
+ describe '#key' do
+ subject { cache.key }
+
+ it 'returns the next version of the cache' do
+ is_expected.to start_with("highlighted-diff-files:#{cache.diffable.cache_key}:2")
+ end
+
+ context 'when feature flag is disabled' do
+ before do
+ stub_feature_flags(improved_merge_diff_highlighting: false)
+ end
+
+ it 'returns the original version of the cache' do
+ is_expected.to start_with("highlighted-diff-files:#{cache.diffable.cache_key}:1")
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/diff/inline_diff_spec.rb b/spec/lib/gitlab/diff/inline_diff_spec.rb
index 35284e952f7..dce655d5690 100644
--- a/spec/lib/gitlab/diff/inline_diff_spec.rb
+++ b/spec/lib/gitlab/diff/inline_diff_spec.rb
@@ -37,6 +37,33 @@ RSpec.describe Gitlab::Diff::InlineDiff do
it 'can handle unchanged empty lines' do
expect { described_class.for_lines(['- bar', '+ baz', '']) }.not_to raise_error
end
+
+ context 'when lines have multiple changes' do
+ let(:diff) do
+ <<~EOF
+ - Hello, how are you?
+ + Hi, how are you doing?
+ EOF
+ end
+
+ let(:subject) { described_class.for_lines(diff.lines) }
+
+ it 'finds all inline diffs' do
+ expect(subject[0]).to eq([3..6])
+ expect(subject[1]).to eq([3..3, 17..22])
+ end
+
+ context 'when feature flag is disabled' do
+ before do
+ stub_feature_flags(improved_merge_diff_highlighting: false)
+ end
+
+ it 'finds all inline diffs' do
+ expect(subject[0]).to eq([3..19])
+ expect(subject[1]).to eq([3..22])
+ end
+ end
+ end
end
describe "#inline_diffs" do
diff --git a/spec/lib/gitlab/experimentation/controller_concern_spec.rb b/spec/lib/gitlab/experimentation/controller_concern_spec.rb
index c47f71c207d..1cebe37bea5 100644
--- a/spec/lib/gitlab/experimentation/controller_concern_spec.rb
+++ b/spec/lib/gitlab/experimentation/controller_concern_spec.rb
@@ -10,6 +10,10 @@ RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do
use_backwards_compatible_subject_index: true
},
test_experiment: {
+ tracking_category: 'Team',
+ rollout_strategy: rollout_strategy
+ },
+ my_experiment: {
tracking_category: 'Team'
}
}
@@ -20,6 +24,7 @@ RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do
end
let(:enabled_percentage) { 10 }
+ let(:rollout_strategy) { nil }
controller(ApplicationController) do
include Gitlab::Experimentation::ControllerConcern
@@ -117,6 +122,7 @@ RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do
end
context 'when subject is given' do
+ let(:rollout_strategy) { :user }
let(:user) { build(:user) }
it 'uses the subject' do
@@ -244,6 +250,7 @@ RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do
it "provides the subject's hashed global_id as label" do
experiment_subject = double(:subject, to_global_id: 'abc')
+ allow(Gitlab::Experimentation).to receive(:valid_subject_for_rollout_strategy?).and_return(true)
controller.track_experiment_event(:test_experiment, 'start', 1, subject: experiment_subject)
@@ -420,6 +427,26 @@ RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do
controller.record_experiment_user(:test_experiment, context)
end
+
+ context 'with a cookie based rollout strategy' do
+ it 'calls tracking_group with a nil subject' do
+ expect(controller).to receive(:tracking_group).with(:test_experiment, nil, subject: nil).and_return(:experimental)
+ allow(::Experiment).to receive(:add_user).with(:test_experiment, :experimental, user, context)
+
+ controller.record_experiment_user(:test_experiment, context)
+ end
+ end
+
+ context 'with a user based rollout strategy' do
+ let(:rollout_strategy) { :user }
+
+ it 'calls tracking_group with a user subject' do
+ expect(controller).to receive(:tracking_group).with(:test_experiment, nil, subject: user).and_return(:experimental)
+ allow(::Experiment).to receive(:add_user).with(:test_experiment, :experimental, user, context)
+
+ controller.record_experiment_user(:test_experiment, context)
+ end
+ end
end
context 'the user is part of the control group' do
diff --git a/spec/lib/gitlab/experimentation/experiment_spec.rb b/spec/lib/gitlab/experimentation/experiment_spec.rb
index 008e6699597..94dbf1d7e4b 100644
--- a/spec/lib/gitlab/experimentation/experiment_spec.rb
+++ b/spec/lib/gitlab/experimentation/experiment_spec.rb
@@ -9,7 +9,8 @@ RSpec.describe Gitlab::Experimentation::Experiment do
let(:params) do
{
tracking_category: 'Category1',
- use_backwards_compatible_subject_index: true
+ use_backwards_compatible_subject_index: true,
+ rollout_strategy: nil
}
end
diff --git a/spec/lib/gitlab/experimentation_spec.rb b/spec/lib/gitlab/experimentation_spec.rb
index b503960b8c7..4ef8a75de4f 100644
--- a/spec/lib/gitlab/experimentation_spec.rb
+++ b/spec/lib/gitlab/experimentation_spec.rb
@@ -15,8 +15,7 @@ RSpec.describe Gitlab::Experimentation::EXPERIMENTS do
:invite_members_empty_group_version_a,
:contact_sales_btn_in_app,
:customize_homepage,
- :group_only_trials,
- :default_to_issues_board
+ :group_only_trials
]
backwards_compatible_experiment_keys = described_class.filter { |_, v| v[:use_backwards_compatible_subject_index] }.keys
@@ -27,6 +26,8 @@ RSpec.describe Gitlab::Experimentation::EXPERIMENTS do
end
RSpec.describe Gitlab::Experimentation do
+ using RSpec::Parameterized::TableSyntax
+
before do
stub_const('Gitlab::Experimentation::EXPERIMENTS', {
backwards_compatible_test_experiment: {
@@ -35,6 +36,10 @@ RSpec.describe Gitlab::Experimentation do
},
test_experiment: {
tracking_category: 'Team'
+ },
+ tabular_experiment: {
+ tracking_category: 'Team',
+ rollout_strategy: rollout_strategy
}
})
@@ -46,6 +51,7 @@ RSpec.describe Gitlab::Experimentation do
end
let(:enabled_percentage) { 10 }
+ let(:rollout_strategy) { nil }
describe '.get_experiment' do
subject { described_class.get_experiment(:test_experiment) }
@@ -175,4 +181,59 @@ RSpec.describe Gitlab::Experimentation do
end
end
end
+
+ describe '.log_invalid_rollout' do
+ subject { described_class.log_invalid_rollout(:test_experiment, 1) }
+
+ before do
+ allow(described_class).to receive(:valid_subject_for_rollout_strategy?).and_return(valid)
+ end
+
+ context 'subject is not valid for experiment' do
+ let(:valid) { false }
+
+ it 'logs a warning message' do
+ expect_next_instance_of(Gitlab::ExperimentationLogger) do |logger|
+ expect(logger)
+ .to receive(:warn)
+ .with(
+ message: 'Subject must conform to the rollout strategy',
+ experiment_key: :test_experiment,
+ subject: 'Integer',
+ rollout_strategy: :cookie
+ )
+ end
+
+ subject
+ end
+ end
+
+ context 'subject is valid for experiment' do
+ let(:valid) { true }
+
+ it 'does not log a warning message' do
+ expect(Gitlab::ExperimentationLogger).not_to receive(:build)
+
+ subject
+ end
+ end
+ end
+
+ describe '.valid_subject_for_rollout_strategy?' do
+ subject { described_class.valid_subject_for_rollout_strategy?(:tabular_experiment, experiment_subject) }
+
+ where(:rollout_strategy, :experiment_subject, :result) do
+ :cookie | nil | true
+ nil | nil | true
+ :cookie | 'string' | true
+ nil | User.new | false
+ :user | User.new | true
+ :group | User.new | false
+ :group | Group.new | true
+ end
+
+ with_them do
+ it { is_expected.to be(result) }
+ end
+ end
end
diff --git a/spec/lib/gitlab/file_finder_spec.rb b/spec/lib/gitlab/file_finder_spec.rb
index 8d6df62b3f6..0b5303f22b4 100644
--- a/spec/lib/gitlab/file_finder_spec.rb
+++ b/spec/lib/gitlab/file_finder_spec.rb
@@ -53,6 +53,14 @@ RSpec.describe Gitlab::FileFinder do
end
end
+ context 'with white space in the path' do
+ it 'filters by path correctly' do
+ results = subject.find('directory path:"with space/README.md"')
+
+ expect(results.count).to eq(1)
+ end
+ end
+
it 'does not cause N+1 query' do
expect(Gitlab::GitalyClient).to receive(:call).at_most(10).times.and_call_original
diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb
index 8961cdcae7d..49f1e6e994f 100644
--- a/spec/lib/gitlab/git/commit_spec.rb
+++ b/spec/lib/gitlab/git/commit_spec.rb
@@ -720,7 +720,8 @@ RSpec.describe Gitlab::Git::Commit, :seed_helper do
committer_name: "Dmitriy Zaporozhets",
id: SeedRepo::Commit::ID,
message: "tree css fixes",
- parent_ids: ["874797c3a73b60d2187ed6e2fcabd289ff75171e"]
+ parent_ids: ["874797c3a73b60d2187ed6e2fcabd289ff75171e"],
+ trailers: {}
}
end
end
diff --git a/spec/lib/gitlab/git/diff_spec.rb b/spec/lib/gitlab/git/diff_spec.rb
index 783f0a9ccf7..17bb83d0f2f 100644
--- a/spec/lib/gitlab/git/diff_spec.rb
+++ b/spec/lib/gitlab/git/diff_spec.rb
@@ -100,6 +100,13 @@ EOT
expect(diff.diff).to be_empty
expect(diff).to be_too_large
end
+
+ it 'logs the event' do
+ expect(Gitlab::Metrics).to receive(:add_event)
+ .with(:patch_hard_limit_bytes_hit)
+
+ diff
+ end
end
context 'using a collapsable diff that is too large' do
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index ef9b5a30c86..cc1b1ceadcf 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -1894,8 +1894,11 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
it 'removes the remote' do
repository_rugged.remotes.create(remote_name, url)
- repository.remove_remote(remote_name)
+ expect(repository.remove_remote(remote_name)).to be true
+ # Since we deleted the remote via Gitaly, Rugged doesn't know
+ # this changed underneath it. Let's refresh the Rugged repo.
+ repository_rugged = Rugged::Repository.new(repository_path)
expect(repository_rugged.remotes[remote_name]).to be_nil
end
end
diff --git a/spec/lib/gitlab/graphql/pagination/connections_spec.rb b/spec/lib/gitlab/graphql/pagination/connections_spec.rb
new file mode 100644
index 00000000000..e89e5c17644
--- /dev/null
+++ b/spec/lib/gitlab/graphql/pagination/connections_spec.rb
@@ -0,0 +1,97 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+# Tests that our connections are correctly mapped.
+RSpec.describe ::Gitlab::Graphql::Pagination::Connections do
+ include GraphqlHelpers
+
+ before(:all) do
+ ActiveRecord::Schema.define do
+ create_table :testing_pagination_nodes, force: true do |t|
+ t.integer :value, null: false
+ end
+ end
+ end
+
+ after(:all) do
+ ActiveRecord::Schema.define do
+ drop_table :testing_pagination_nodes, force: true
+ end
+ end
+
+ let_it_be(:node_model) do
+ Class.new(ActiveRecord::Base) do
+ self.table_name = 'testing_pagination_nodes'
+ end
+ end
+
+ let(:query_string) { 'query { items(first: 2) { nodes { value } } }' }
+ let(:user) { nil }
+
+ let(:node) { Struct.new(:value) }
+ let(:node_type) do
+ Class.new(::GraphQL::Schema::Object) do
+ graphql_name 'Node'
+ field :value, GraphQL::INT_TYPE, null: false
+ end
+ end
+
+ let(:query_type) do
+ item_values = nodes
+
+ query_factory do |t|
+ t.field :items, node_type.connection_type, null: true
+
+ t.define_method :items do
+ item_values
+ end
+ end
+ end
+
+ shared_examples 'it maps to a specific connection class' do |connection_type|
+ let(:raw_values) { [1, 7, 42] }
+
+ it "maps to #{connection_type.name}" do
+ expect(connection_type).to receive(:new).and_call_original
+
+ results = execute_query(query_type).to_h
+
+ expect(graphql_dig_at(results, :data, :items, :nodes, :value)).to eq [1, 7]
+ end
+ end
+
+ describe 'OffsetPaginatedRelation' do
+ before do
+ # Expect to be ordered by an explicit ordering.
+ raw_values.each_with_index { |value, id| node_model.create!(id: id, value: value) }
+ end
+
+ let(:nodes) { ::Gitlab::Graphql::Pagination::OffsetPaginatedRelation.new(node_model.order(value: :asc)) }
+
+ include_examples 'it maps to a specific connection class', Gitlab::Graphql::Pagination::OffsetActiveRecordRelationConnection
+ end
+
+ describe 'ActiveRecord::Relation' do
+ before do
+ # Expect to be ordered by ID descending
+ [3, 2, 1].zip(raw_values) { |id, value| node_model.create!(id: id, value: value) }
+ end
+
+ let(:nodes) { node_model.all }
+
+ include_examples 'it maps to a specific connection class', Gitlab::Graphql::Pagination::Keyset::Connection
+ end
+
+ describe 'ExternallyPaginatedArray' do
+ let(:nodes) { ::Gitlab::Graphql::ExternallyPaginatedArray.new(nil, nil, node.new(1), node.new(7)) }
+
+ include_examples 'it maps to a specific connection class', Gitlab::Graphql::Pagination::ExternallyPaginatedArrayConnection
+ end
+
+ describe 'Array' do
+ let(:nodes) { raw_values.map { |x| node.new(x) } }
+
+ include_examples 'it maps to a specific connection class', Gitlab::Graphql::Pagination::ArrayConnection
+ end
+end
diff --git a/spec/lib/gitlab/hook_data/group_builder_spec.rb b/spec/lib/gitlab/hook_data/group_builder_spec.rb
new file mode 100644
index 00000000000..d7347ff99d4
--- /dev/null
+++ b/spec/lib/gitlab/hook_data/group_builder_spec.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::HookData::GroupBuilder do
+ let_it_be(:group) { create(:group) }
+
+ describe '#build' do
+ let(:data) { described_class.new(group).build(event) }
+ let(:event_name) { data[:event_name] }
+ let(:attributes) do
+ [
+ :event_name, :created_at, :updated_at, :name, :path, :full_path, :group_id
+ ]
+ end
+
+ context 'data' do
+ shared_examples_for 'includes the required attributes' do
+ it 'includes the required attributes' do
+ expect(data).to include(*attributes)
+
+ expect(data[:name]).to eq(group.name)
+ expect(data[:path]).to eq(group.path)
+ expect(data[:full_path]).to eq(group.full_path)
+ expect(data[:group_id]).to eq(group.id)
+ expect(data[:created_at]).to eq(group.created_at.xmlschema)
+ expect(data[:updated_at]).to eq(group.updated_at.xmlschema)
+ end
+ end
+
+ shared_examples_for 'does not include old path attributes' do
+ it 'does not include old path attributes' do
+ expect(data).not_to include(:old_path, :old_full_path)
+ end
+ end
+
+ context 'on create' do
+ let(:event) { :create }
+
+ it { expect(event_name).to eq('group_create') }
+ it_behaves_like 'includes the required attributes'
+ it_behaves_like 'does not include old path attributes'
+ end
+
+ context 'on destroy' do
+ let(:event) { :destroy }
+
+ it { expect(event_name).to eq('group_destroy') }
+ it_behaves_like 'includes the required attributes'
+ it_behaves_like 'does not include old path attributes'
+ end
+
+ context 'on rename' do
+ let(:event) { :rename }
+
+ it { expect(event_name).to eq('group_rename') }
+ it_behaves_like 'includes the required attributes'
+
+ it 'includes old path details' do
+ allow(group).to receive(:path_before_last_save).and_return('old-path')
+
+ expect(data[:old_path]).to eq(group.path_before_last_save)
+ expect(data[:old_full_path]).to eq(group.path_before_last_save)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/hook_data/subgroup_builder_spec.rb b/spec/lib/gitlab/hook_data/subgroup_builder_spec.rb
new file mode 100644
index 00000000000..89e5dffd7b4
--- /dev/null
+++ b/spec/lib/gitlab/hook_data/subgroup_builder_spec.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::HookData::SubgroupBuilder do
+ let_it_be(:parent_group) { create(:group) }
+ let_it_be(:subgroup) { create(:group, parent: parent_group) }
+
+ describe '#build' do
+ let(:data) { described_class.new(subgroup).build(event) }
+ let(:event_name) { data[:event_name] }
+ let(:attributes) do
+ [
+ :event_name, :created_at, :updated_at, :name, :path, :full_path, :group_id,
+ :parent_group_id, :parent_name, :parent_path, :parent_full_path
+ ]
+ end
+
+ context 'data' do
+ shared_examples_for 'includes the required attributes' do
+ it 'includes the required attributes' do
+ expect(data).to include(*attributes)
+
+ expect(data[:name]).to eq(subgroup.name)
+ expect(data[:path]).to eq(subgroup.path)
+ expect(data[:full_path]).to eq(subgroup.full_path)
+ expect(data[:group_id]).to eq(subgroup.id)
+ expect(data[:created_at]).to eq(subgroup.created_at.xmlschema)
+ expect(data[:updated_at]).to eq(subgroup.updated_at.xmlschema)
+ expect(data[:parent_name]).to eq(parent_group.name)
+ expect(data[:parent_path]).to eq(parent_group.path)
+ expect(data[:parent_full_path]).to eq(parent_group.full_path)
+ expect(data[:parent_group_id]).to eq(parent_group.id)
+ end
+ end
+
+ context 'on create' do
+ let(:event) { :create }
+
+ it { expect(event_name).to eq('subgroup_create') }
+ it_behaves_like 'includes the required attributes'
+ end
+
+ context 'on destroy' do
+ let(:event) { :destroy }
+
+ it { expect(event_name).to eq('subgroup_destroy') }
+ it_behaves_like 'includes the required attributes'
+ 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 825513bdfc5..2d616ec8862 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -146,6 +146,7 @@ merge_requests:
- merge_user
- merge_request_diffs
- merge_request_diff
+- merge_head_diff
- merge_request_context_commits
- merge_request_context_commit_diff_files
- events
@@ -544,7 +545,7 @@ project:
- daily_build_group_report_results
- jira_imports
- compliance_framework_setting
-- compliance_management_frameworks
+- compliance_management_framework
- metrics_users_starred_dashboards
- alert_management_alerts
- repository_storage_moves
@@ -561,6 +562,7 @@ project:
- exported_protected_branches
- incident_management_oncall_schedules
- debian_distributions
+- merge_request_metrics
award_emoji:
- awardable
- user
@@ -589,6 +591,7 @@ lfs_file_locks:
project_badges:
- project
metrics:
+- target_project
- merge_request
- latest_closed_by
- merged_by
diff --git a/spec/lib/gitlab/import_export/design_repo_restorer_spec.rb b/spec/lib/gitlab/import_export/design_repo_restorer_spec.rb
index b311a02833c..6680f4e7a03 100644
--- a/spec/lib/gitlab/import_export/design_repo_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/design_repo_restorer_spec.rb
@@ -11,12 +11,12 @@ RSpec.describe Gitlab::ImportExport::DesignRepoRestorer do
let!(:project) { create(:project) }
let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
let(:shared) { project.import_export_shared }
- let(:bundler) { Gitlab::ImportExport::DesignRepoSaver.new(project: project_with_design_repo, shared: shared) }
+ let(:bundler) { Gitlab::ImportExport::DesignRepoSaver.new(exportable: project_with_design_repo, shared: shared) }
let(:bundle_path) { File.join(shared.export_path, Gitlab::ImportExport.design_repo_bundle_filename) }
let(:restorer) do
described_class.new(path_to_bundle: bundle_path,
shared: shared,
- project: project)
+ importable: project)
end
before do
diff --git a/spec/lib/gitlab/import_export/design_repo_saver_spec.rb b/spec/lib/gitlab/import_export/design_repo_saver_spec.rb
index 2575d209db5..5501e3dee5a 100644
--- a/spec/lib/gitlab/import_export/design_repo_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/design_repo_saver_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe Gitlab::ImportExport::DesignRepoSaver do
let!(:project) { create(:project, :design_repo) }
let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
let(:shared) { project.import_export_shared }
- let(:design_bundler) { described_class.new(project: project, shared: shared) }
+ let(:design_bundler) { described_class.new(exportable: project, shared: shared) }
before do
project.add_maintainer(user)
diff --git a/spec/lib/gitlab/import_export/fork_spec.rb b/spec/lib/gitlab/import_export/fork_spec.rb
index ef7394053b9..65c28a8b8a2 100644
--- a/spec/lib/gitlab/import_export/fork_spec.rb
+++ b/spec/lib/gitlab/import_export/fork_spec.rb
@@ -12,11 +12,11 @@ RSpec.describe 'forked project import' do
let(:shared) { project.import_export_shared }
let(:forked_from_project) { create(:project, :repository) }
let(:forked_project) { fork_project(project_with_repo, nil, repository: true) }
- let(:repo_saver) { Gitlab::ImportExport::RepoSaver.new(project: project_with_repo, shared: shared) }
+ let(:repo_saver) { Gitlab::ImportExport::RepoSaver.new(exportable: project_with_repo, shared: shared) }
let(:bundle_path) { File.join(shared.export_path, Gitlab::ImportExport.project_bundle_filename) }
let(:repo_restorer) do
- Gitlab::ImportExport::RepoRestorer.new(path_to_bundle: bundle_path, shared: shared, project: project)
+ Gitlab::ImportExport::RepoRestorer.new(path_to_bundle: bundle_path, shared: shared, importable: project)
end
let!(:merge_request) do
diff --git a/spec/lib/gitlab/import_export/group/tree_restorer_spec.rb b/spec/lib/gitlab/import_export/group/tree_restorer_spec.rb
index 2794acb8980..d2153221e8f 100644
--- a/spec/lib/gitlab/import_export/group/tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/group/tree_restorer_spec.rb
@@ -21,6 +21,7 @@ RSpec.describe Gitlab::ImportExport::Group::TreeRestorer do
group_tree_restorer = described_class.new(user: user, shared: @shared, group: @group)
expect(group_tree_restorer.restore).to be_truthy
+ expect(group_tree_restorer.groups_mapping).not_to be_empty
end
end
diff --git a/spec/lib/gitlab/import_export/importer_spec.rb b/spec/lib/gitlab/import_export/importer_spec.rb
index 75db3167ebc..20f0f6af6f3 100644
--- a/spec/lib/gitlab/import_export/importer_spec.rb
+++ b/spec/lib/gitlab/import_export/importer_spec.rb
@@ -69,8 +69,8 @@ RSpec.describe Gitlab::ImportExport::Importer do
repo_path = File.join(shared.export_path, Gitlab::ImportExport.project_bundle_filename)
restorer = double(Gitlab::ImportExport::RepoRestorer)
- expect(Gitlab::ImportExport::RepoRestorer).to receive(:new).with(path_to_bundle: repo_path, shared: shared, project: project).and_return(restorer)
- expect(Gitlab::ImportExport::RepoRestorer).to receive(:new).with(path_to_bundle: wiki_repo_path, shared: shared, project: ProjectWiki.new(project)).and_return(restorer)
+ expect(Gitlab::ImportExport::RepoRestorer).to receive(:new).with(path_to_bundle: repo_path, shared: shared, importable: project).and_return(restorer)
+ expect(Gitlab::ImportExport::RepoRestorer).to receive(:new).with(path_to_bundle: wiki_repo_path, shared: shared, importable: ProjectWiki.new(project)).and_return(restorer)
expect(Gitlab::ImportExport::RepoRestorer).to receive(:new).and_call_original
expect(restorer).to receive(:restore).and_return(true).twice
diff --git a/spec/lib/gitlab/import_export/repo_restorer_spec.rb b/spec/lib/gitlab/import_export/repo_restorer_spec.rb
index a6b917457c2..fe43a23e242 100644
--- a/spec/lib/gitlab/import_export/repo_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/repo_restorer_spec.rb
@@ -27,10 +27,10 @@ RSpec.describe Gitlab::ImportExport::RepoRestorer do
end
describe 'bundle a project Git repo' do
- let(:bundler) { Gitlab::ImportExport::RepoSaver.new(project: project_with_repo, shared: shared) }
+ let(:bundler) { Gitlab::ImportExport::RepoSaver.new(exportable: project_with_repo, shared: shared) }
let(:bundle_path) { File.join(shared.export_path, Gitlab::ImportExport.project_bundle_filename) }
- subject { described_class.new(path_to_bundle: bundle_path, shared: shared, project: project) }
+ subject { described_class.new(path_to_bundle: bundle_path, shared: shared, importable: project) }
after do
Gitlab::Shell.new.remove_repository(project.repository_storage, project.disk_path)
@@ -62,10 +62,10 @@ RSpec.describe Gitlab::ImportExport::RepoRestorer do
end
describe 'restore a wiki Git repo' do
- let(:bundler) { Gitlab::ImportExport::WikiRepoSaver.new(project: project_with_repo, shared: shared) }
+ let(:bundler) { Gitlab::ImportExport::WikiRepoSaver.new(exportable: project_with_repo, shared: shared) }
let(:bundle_path) { File.join(shared.export_path, Gitlab::ImportExport.wiki_repo_bundle_filename) }
- subject { described_class.new(path_to_bundle: bundle_path, shared: shared, project: ProjectWiki.new(project)) }
+ subject { described_class.new(path_to_bundle: bundle_path, shared: shared, importable: ProjectWiki.new(project)) }
after do
Gitlab::Shell.new.remove_repository(project.wiki.repository_storage, project.wiki.disk_path)
@@ -83,7 +83,7 @@ RSpec.describe Gitlab::ImportExport::RepoRestorer do
describe 'no wiki in the bundle' do
let!(:project_without_wiki) { create(:project) }
- let(:bundler) { Gitlab::ImportExport::WikiRepoSaver.new(project: project_without_wiki, shared: shared) }
+ let(:bundler) { Gitlab::ImportExport::WikiRepoSaver.new(exportable: project_without_wiki, shared: shared) }
it 'does not creates an empty wiki' do
expect(subject.restore).to be true
diff --git a/spec/lib/gitlab/import_export/repo_saver_spec.rb b/spec/lib/gitlab/import_export/repo_saver_spec.rb
index 73d51000c67..52001e778d6 100644
--- a/spec/lib/gitlab/import_export/repo_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/repo_saver_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe Gitlab::ImportExport::RepoSaver do
let!(:project) { create(:project, :repository) }
let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
let(:shared) { project.import_export_shared }
- let(:bundler) { described_class.new(project: project, shared: shared) }
+ let(:bundler) { described_class.new(exportable: project, shared: shared) }
before do
project.add_maintainer(user)
@@ -25,6 +25,14 @@ RSpec.describe Gitlab::ImportExport::RepoSaver do
expect(bundler.save).to be true
end
+ it 'creates the directory for the repository' do
+ allow(bundler).to receive(:bundle_full_path).and_return('/foo/bar/file.tar.gz')
+
+ expect(FileUtils).to receive(:mkdir_p).with('/foo/bar', anything)
+
+ bundler.save # rubocop:disable Rails/SaveBang
+ end
+
context 'when the repo is empty' do
let!(:project) { create(:project) }
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index a93ee051ccf..e301be47d68 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -220,6 +220,7 @@ MergeRequestDiff:
- commits_count
- files_count
- sorted
+- diff_type
MergeRequestDiffCommit:
- merge_request_diff_id
- relative_order
@@ -231,6 +232,7 @@ MergeRequestDiffCommit:
- committer_name
- committer_email
- message
+- trailers
MergeRequestDiffFile:
- merge_request_diff_id
- relative_order
@@ -255,6 +257,7 @@ MergeRequestContextCommit:
- committer_email
- message
- merge_request_id
+- trailers
MergeRequestContextCommitDiffFile:
- sha
- relative_order
@@ -580,6 +583,7 @@ ProjectFeature:
- requirements_access_level
- analytics_access_level
- operations_access_level
+- security_and_compliance_access_level
- created_at
- updated_at
ProtectedBranch::MergeAccessLevel:
diff --git a/spec/lib/gitlab/import_export/saver_spec.rb b/spec/lib/gitlab/import_export/saver_spec.rb
index 865c7e57b5a..877474dd862 100644
--- a/spec/lib/gitlab/import_export/saver_spec.rb
+++ b/spec/lib/gitlab/import_export/saver_spec.rb
@@ -6,7 +6,8 @@ require 'fileutils'
RSpec.describe Gitlab::ImportExport::Saver do
let!(:project) { create(:project, :public, name: 'project') }
let(:base_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
- let(:export_path) { "#{base_path}/project_tree_saver_spec/export" }
+ let(:archive_path) { "#{base_path}/archive" }
+ let(:export_path) { "#{archive_path}/export" }
let(:shared) { project.import_export_shared }
subject { described_class.new(exportable: project, shared: shared) }
@@ -35,10 +36,13 @@ RSpec.describe Gitlab::ImportExport::Saver do
.to match(%r[\/uploads\/-\/system\/import_export_upload\/export_file.*])
end
- it 'removes tmp files' do
+ it 'removes archive path and keeps base path untouched' do
+ allow(shared).to receive(:archive_path).and_return(archive_path)
+
subject.save
- expect(FileUtils).to have_received(:rm_rf).with(base_path)
- expect(Dir.exist?(base_path)).to eq(false)
+ expect(FileUtils).not_to have_received(:rm_rf).with(base_path)
+ expect(FileUtils).to have_received(:rm_rf).with(archive_path)
+ expect(Dir.exist?(archive_path)).to eq(false)
end
end
diff --git a/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb b/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb
index 778d0859bf1..540f90e7804 100644
--- a/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe Gitlab::ImportExport::WikiRepoSaver do
let_it_be(:project) { create(:project, :wiki_repo) }
let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
let(:shared) { project.import_export_shared }
- let(:wiki_bundler) { described_class.new(project: project, shared: shared) }
+ let(:wiki_bundler) { described_class.new(exportable: project, shared: shared) }
let!(:project_wiki) { ProjectWiki.new(project, user) }
before do
diff --git a/spec/lib/gitlab/instrumentation/redis_cluster_validator_spec.rb b/spec/lib/gitlab/instrumentation/redis_cluster_validator_spec.rb
index 2ca7465e775..e4af3f77d5d 100644
--- a/spec/lib/gitlab/instrumentation/redis_cluster_validator_spec.rb
+++ b/spec/lib/gitlab/instrumentation/redis_cluster_validator_spec.rb
@@ -53,6 +53,7 @@ RSpec.describe Gitlab::Instrumentation::RedisClusterValidator do
:del | [%w(foo bar)] | true # Arguments can be a nested array
:del | %w(foo foo) | false
:hset | %w(foo bar) | false # Not a multi-key command
+ :mget | [] | false # This is invalid, but not because it's a cross-slot command
end
with_them do
diff --git a/spec/lib/gitlab/instrumentation_helper_spec.rb b/spec/lib/gitlab/instrumentation_helper_spec.rb
index c00b0fdf043..48b76c4cdbf 100644
--- a/spec/lib/gitlab/instrumentation_helper_spec.rb
+++ b/spec/lib/gitlab/instrumentation_helper_spec.rb
@@ -37,7 +37,11 @@ RSpec.describe Gitlab::InstrumentationHelper do
:redis_shared_state_write_bytes,
:db_count,
:db_write_count,
- :db_cached_count
+ :db_cached_count,
+ :external_http_count,
+ :external_http_duration_s,
+ :rack_attack_redis_count,
+ :rack_attack_redis_duration_s
]
expect(described_class.keys).to eq(expected_keys)
diff --git a/spec/lib/gitlab/kas_spec.rb b/spec/lib/gitlab/kas_spec.rb
index ce22f36e9fd..01ced407883 100644
--- a/spec/lib/gitlab/kas_spec.rb
+++ b/spec/lib/gitlab/kas_spec.rb
@@ -58,4 +58,48 @@ RSpec.describe Gitlab::Kas do
end
end
end
+
+ describe '.included_in_gitlab_com_rollout?' do
+ let_it_be(:project) { create(:project) }
+
+ context 'not GitLab.com' do
+ before do
+ allow(Gitlab).to receive(:com?).and_return(false)
+ end
+
+ it 'returns true' do
+ expect(described_class.included_in_gitlab_com_rollout?(project)).to be_truthy
+ end
+ end
+
+ context 'GitLab.com' do
+ before do
+ allow(Gitlab).to receive(:com?).and_return(true)
+ end
+
+ context 'kubernetes_agent_on_gitlab_com feature flag disabled' do
+ before do
+ stub_feature_flags(kubernetes_agent_on_gitlab_com: false)
+ end
+
+ it 'returns false' do
+ expect(described_class.included_in_gitlab_com_rollout?(project)).to be_falsey
+ end
+ end
+
+ context 'kubernetes_agent_on_gitlab_com feature flag enabled' do
+ before do
+ stub_feature_flags(kubernetes_agent_on_gitlab_com: project)
+ end
+
+ it 'returns true' do
+ expect(described_class.included_in_gitlab_com_rollout?(project)).to be_truthy
+ end
+
+ it 'returns false for another project' do
+ expect(described_class.included_in_gitlab_com_rollout?(create(:project))).to be_falsey
+ end
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/metrics/subscribers/external_http_spec.rb b/spec/lib/gitlab/metrics/subscribers/external_http_spec.rb
new file mode 100644
index 00000000000..5bcaf8fbc47
--- /dev/null
+++ b/spec/lib/gitlab/metrics/subscribers/external_http_spec.rb
@@ -0,0 +1,172 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Metrics::Subscribers::ExternalHttp, :request_store do
+ let(:transaction) { Gitlab::Metrics::Transaction.new }
+ let(:subscriber) { described_class.new }
+
+ let(:event_1) do
+ double(:event, payload: {
+ method: 'POST', code: "200", duration: 0.321,
+ scheme: 'https', host: 'gitlab.com', port: 80, path: '/api/v4/projects',
+ query: 'current=true'
+ })
+ end
+
+ let(:event_2) do
+ double(:event, payload: {
+ method: 'GET', code: "301", duration: 0.12,
+ scheme: 'http', host: 'gitlab.com', port: 80, path: '/api/v4/projects/2',
+ query: 'current=true'
+ })
+ end
+
+ let(:event_3) do
+ double(:event, payload: {
+ method: 'POST', duration: 5.3,
+ scheme: 'http', host: 'gitlab.com', port: 80, path: '/api/v4/projects/2/issues',
+ query: 'current=true',
+ exception_object: Net::ReadTimeout.new
+ })
+ end
+
+ describe '.detail_store' do
+ context 'when external HTTP detail store is empty' do
+ before do
+ Gitlab::SafeRequestStore[:peek_enabled] = true
+ end
+
+ it 'returns an empty array' do
+ expect(described_class.detail_store).to eql([])
+ end
+ end
+
+ context 'when the performance bar is not enabled' do
+ it 'returns an empty array' do
+ expect(described_class.detail_store).to eql([])
+ end
+ end
+
+ context 'when external HTTP detail store has some values' do
+ before do
+ Gitlab::SafeRequestStore[:peek_enabled] = true
+ Gitlab::SafeRequestStore[:external_http_detail_store] = [{
+ method: 'POST', code: "200", duration: 0.321
+ }]
+ end
+
+ it 'returns the external http detailed store' do
+ expect(described_class.detail_store).to eql([{ method: 'POST', code: "200", duration: 0.321 }])
+ end
+ end
+ end
+
+ describe '.payload' do
+ context 'when SafeRequestStore does not have any item from external HTTP' do
+ it 'returns an empty array' do
+ expect(described_class.payload).to eql(external_http_count: 0, external_http_duration_s: 0.0)
+ end
+ end
+
+ context 'when external HTTP recorded some values' do
+ before do
+ Gitlab::SafeRequestStore[:external_http_count] = 7
+ Gitlab::SafeRequestStore[:external_http_duration_s] = 1.2
+ end
+
+ it 'returns the external http detailed store' do
+ expect(described_class.payload).to eql(external_http_count: 7, external_http_duration_s: 1.2)
+ end
+ end
+ end
+
+ describe '#request' do
+ before do
+ Gitlab::SafeRequestStore[:peek_enabled] = true
+ allow(subscriber).to receive(:current_transaction).and_return(transaction)
+ end
+
+ it 'tracks external HTTP request count' do
+ expect(transaction).to receive(:increment)
+ .with(:gitlab_external_http_total, 1, { code: "200", method: "POST" })
+ expect(transaction).to receive(:increment)
+ .with(:gitlab_external_http_total, 1, { code: "301", method: "GET" })
+
+ subscriber.request(event_1)
+ subscriber.request(event_2)
+ end
+
+ it 'tracks external HTTP duration' do
+ expect(transaction).to receive(:observe)
+ .with(:gitlab_external_http_duration_seconds, 0.321)
+ expect(transaction).to receive(:observe)
+ .with(:gitlab_external_http_duration_seconds, 0.12)
+ expect(transaction).to receive(:observe)
+ .with(:gitlab_external_http_duration_seconds, 5.3)
+
+ subscriber.request(event_1)
+ subscriber.request(event_2)
+ subscriber.request(event_3)
+ end
+
+ it 'tracks external HTTP exceptions' do
+ expect(transaction).to receive(:increment)
+ .with(:gitlab_external_http_total, 1, { code: 'undefined', method: "POST" })
+ expect(transaction).to receive(:increment)
+ .with(:gitlab_external_http_exception_total, 1)
+
+ subscriber.request(event_3)
+ end
+
+ it 'stores per-request counters' do
+ subscriber.request(event_1)
+ subscriber.request(event_2)
+ subscriber.request(event_3)
+
+ expect(Gitlab::SafeRequestStore[:external_http_count]).to eq(3)
+ expect(Gitlab::SafeRequestStore[:external_http_duration_s]).to eq(5.741) # 0.321 + 0.12 + 5.3
+ end
+
+ it 'stores a portion of events into the detail store' do
+ subscriber.request(event_1)
+ subscriber.request(event_2)
+ subscriber.request(event_3)
+
+ expect(Gitlab::SafeRequestStore[:external_http_detail_store].length).to eq(3)
+ expect(Gitlab::SafeRequestStore[:external_http_detail_store][0]).to include(
+ method: 'POST', code: "200", duration: 0.321,
+ scheme: 'https', host: 'gitlab.com', port: 80, path: '/api/v4/projects',
+ query: 'current=true', exception_object: nil,
+ backtrace: be_a(Array)
+ )
+ expect(Gitlab::SafeRequestStore[:external_http_detail_store][1]).to include(
+ method: 'GET', code: "301", duration: 0.12,
+ scheme: 'http', host: 'gitlab.com', port: 80, path: '/api/v4/projects/2',
+ query: 'current=true', exception_object: nil,
+ backtrace: be_a(Array)
+ )
+ expect(Gitlab::SafeRequestStore[:external_http_detail_store][2]).to include(
+ method: 'POST', duration: 5.3,
+ scheme: 'http', host: 'gitlab.com', port: 80, path: '/api/v4/projects/2/issues',
+ query: 'current=true',
+ exception_object: be_a(Net::ReadTimeout),
+ backtrace: be_a(Array)
+ )
+ end
+
+ context 'when the performance bar is not enabled' do
+ before do
+ Gitlab::SafeRequestStore.delete(:peek_enabled)
+ end
+
+ it 'does not capture detail store' do
+ subscriber.request(event_1)
+ subscriber.request(event_2)
+ subscriber.request(event_3)
+
+ expect(Gitlab::SafeRequestStore[:external_http_detail_store]).to be(nil)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/subscribers/rack_attack_spec.rb b/spec/lib/gitlab/metrics/subscribers/rack_attack_spec.rb
new file mode 100644
index 00000000000..2d595632772
--- /dev/null
+++ b/spec/lib/gitlab/metrics/subscribers/rack_attack_spec.rb
@@ -0,0 +1,203 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Metrics::Subscribers::RackAttack, :request_store do
+ let(:subscriber) { described_class.new }
+
+ describe '.payload' do
+ context 'when the request store is empty' do
+ it 'returns empty data' do
+ expect(described_class.payload).to eql(
+ rack_attack_redis_count: 0,
+ rack_attack_redis_duration_s: 0.0
+ )
+ end
+ end
+
+ context 'when the request store already has data' do
+ before do
+ Gitlab::SafeRequestStore[:rack_attack_instrumentation] = {
+ rack_attack_redis_count: 10,
+ rack_attack_redis_duration_s: 9.0
+ }
+ end
+
+ it 'returns the accumulated data' do
+ expect(described_class.payload).to eql(
+ rack_attack_redis_count: 10,
+ rack_attack_redis_duration_s: 9.0
+ )
+ end
+ end
+ end
+
+ describe '#redis' do
+ it 'accumulates per-request RackAttack cache usage' do
+ freeze_time do
+ subscriber.redis(
+ ActiveSupport::Notifications::Event.new(
+ 'redis.rack_attack', Time.current, Time.current + 1.second, '1', { operation: 'fetch' }
+ )
+ )
+ subscriber.redis(
+ ActiveSupport::Notifications::Event.new(
+ 'redis.rack_attack', Time.current, Time.current + 2.seconds, '1', { operation: 'write' }
+ )
+ )
+ subscriber.redis(
+ ActiveSupport::Notifications::Event.new(
+ 'redis.rack_attack', Time.current, Time.current + 3.seconds, '1', { operation: 'read' }
+ )
+ )
+ end
+
+ expect(Gitlab::SafeRequestStore[:rack_attack_instrumentation]).to eql(
+ rack_attack_redis_count: 3,
+ rack_attack_redis_duration_s: 6.0
+ )
+ end
+ end
+
+ shared_examples 'log into auth logger' do
+ context 'when matched throttle does not require user information' do
+ let(:event) do
+ ActiveSupport::Notifications::Event.new(
+ event_name, Time.current, Time.current + 2.seconds, '1', request: double(
+ :request,
+ ip: '1.2.3.4',
+ request_method: 'GET',
+ fullpath: '/api/v4/internal/authorized_keys',
+ env: {
+ 'rack.attack.match_type' => match_type,
+ 'rack.attack.matched' => 'throttle_unauthenticated'
+ }
+ )
+ )
+ end
+
+ it 'logs request information' do
+ expect(Gitlab::AuthLogger).to receive(:error).with(
+ include(
+ message: 'Rack_Attack',
+ env: match_type,
+ remote_ip: '1.2.3.4',
+ request_method: 'GET',
+ path: '/api/v4/internal/authorized_keys',
+ matched: 'throttle_unauthenticated'
+ )
+ )
+ subscriber.send(match_type, event)
+ end
+ end
+
+ context 'when matched throttle requires user information' do
+ context 'when user not found' do
+ let(:event) do
+ ActiveSupport::Notifications::Event.new(
+ event_name, Time.current, Time.current + 2.seconds, '1', request: double(
+ :request,
+ ip: '1.2.3.4',
+ request_method: 'GET',
+ fullpath: '/api/v4/internal/authorized_keys',
+ env: {
+ 'rack.attack.match_type' => match_type,
+ 'rack.attack.matched' => 'throttle_authenticated_api',
+ 'rack.attack.match_discriminator' => 'not_exist_user_id'
+ }
+ )
+ )
+ end
+
+ it 'logs request information and user id' do
+ expect(Gitlab::AuthLogger).to receive(:error).with(
+ include(
+ message: 'Rack_Attack',
+ env: match_type,
+ remote_ip: '1.2.3.4',
+ request_method: 'GET',
+ path: '/api/v4/internal/authorized_keys',
+ matched: 'throttle_authenticated_api',
+ user_id: 'not_exist_user_id'
+ )
+ )
+ subscriber.send(match_type, event)
+ end
+ end
+
+ context 'when user found' do
+ let(:user) { create(:user) }
+ let(:event) do
+ ActiveSupport::Notifications::Event.new(
+ event_name, Time.current, Time.current + 2.seconds, '1', request: double(
+ :request,
+ ip: '1.2.3.4',
+ request_method: 'GET',
+ fullpath: '/api/v4/internal/authorized_keys',
+ env: {
+ 'rack.attack.match_type' => match_type,
+ 'rack.attack.matched' => 'throttle_authenticated_api',
+ 'rack.attack.match_discriminator' => user.id
+ }
+ )
+ )
+ end
+
+ it 'logs request information and user meta' do
+ expect(Gitlab::AuthLogger).to receive(:error).with(
+ include(
+ message: 'Rack_Attack',
+ env: match_type,
+ remote_ip: '1.2.3.4',
+ request_method: 'GET',
+ path: '/api/v4/internal/authorized_keys',
+ matched: 'throttle_authenticated_api',
+ user_id: user.id,
+ 'meta.user' => user.username
+ )
+ )
+ subscriber.send(match_type, event)
+ end
+ end
+ end
+ end
+
+ describe '#throttle' do
+ let(:match_type) { :throttle }
+ let(:event_name) { 'throttle.rack_attack' }
+
+ it_behaves_like 'log into auth logger'
+ end
+
+ describe '#blocklist' do
+ let(:match_type) { :blocklist }
+ let(:event_name) { 'blocklist.rack_attack' }
+
+ it_behaves_like 'log into auth logger'
+ end
+
+ describe '#track' do
+ let(:match_type) { :track }
+ let(:event_name) { 'track.rack_attack' }
+
+ it_behaves_like 'log into auth logger'
+ end
+
+ describe '#safelist' do
+ let(:event) do
+ ActiveSupport::Notifications::Event.new(
+ 'safelist.rack_attack', Time.current, Time.current + 2.seconds, '1', request: double(
+ :request,
+ env: {
+ 'rack.attack.matched' => 'throttle_unauthenticated'
+ }
+ )
+ )
+ end
+
+ it 'adds the matched name to safe request store' do
+ subscriber.safelist(event)
+ expect(Gitlab::SafeRequestStore[:instrumentation_throttle_safelist]).to eql('throttle_unauthenticated')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/patch/prependable_spec.rb b/spec/lib/gitlab/patch/prependable_spec.rb
index 8feab57a8f3..5b01bb99fc8 100644
--- a/spec/lib/gitlab/patch/prependable_spec.rb
+++ b/spec/lib/gitlab/patch/prependable_spec.rb
@@ -231,4 +231,22 @@ RSpec.describe Gitlab::Patch::Prependable do
.to raise_error(described_class::MultiplePrependedBlocks)
end
end
+
+ describe 'the extra hack for override verification' do
+ context 'when ENV["STATIC_VERIFICATION"] is not defined' do
+ it 'does not extend ClassMethods onto the defining module' do
+ expect(ee).not_to respond_to(:class_name)
+ end
+ end
+
+ context 'when ENV["STATIC_VERIFICATION"] is defined' do
+ before do
+ stub_env('STATIC_VERIFICATION', 'true')
+ end
+
+ it 'does extend ClassMethods onto the defining module' do
+ expect(ee).to respond_to(:class_name)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/performance_bar/stats_spec.rb b/spec/lib/gitlab/performance_bar/stats_spec.rb
index c34c6f7b31f..ad11eca56d1 100644
--- a/spec/lib/gitlab/performance_bar/stats_spec.rb
+++ b/spec/lib/gitlab/performance_bar/stats_spec.rb
@@ -22,10 +22,12 @@ RSpec.describe Gitlab::PerformanceBar::Stats do
expect(logger).to receive(:info)
.with({ duration_ms: 1.096, filename: 'lib/gitlab/pagination/offset_pagination.rb',
- filenum: 53, method: 'add_pagination_headers', request_id: 'foo', type: :sql })
+ method_path: 'lib/gitlab/pagination/offset_pagination.rb:add_pagination_headers',
+ count: 1, request_id: 'foo', type: :sql })
expect(logger).to receive(:info)
- .with({ duration_ms: 0.817, filename: 'lib/api/helpers.rb',
- filenum: 112, method: 'find_project', request_id: 'foo', type: :sql }).twice
+ .with({ duration_ms: 1.634, filename: 'lib/api/helpers.rb',
+ method_path: 'lib/api/helpers.rb:find_project',
+ count: 2, request_id: 'foo', type: :sql })
subject
end
diff --git a/spec/lib/gitlab/rack_attack/instrumented_cache_store_spec.rb b/spec/lib/gitlab/rack_attack/instrumented_cache_store_spec.rb
new file mode 100644
index 00000000000..2cb31b00f39
--- /dev/null
+++ b/spec/lib/gitlab/rack_attack/instrumented_cache_store_spec.rb
@@ -0,0 +1,89 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::RackAttack::InstrumentedCacheStore do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:store) { ::ActiveSupport::Cache::NullStore.new }
+
+ subject { described_class.new(upstream_store: store)}
+
+ where(:operation, :params, :test_proc) do
+ :fetch | [:key] | ->(s) { s.fetch(:key) }
+ :read | [:key] | ->(s) { s.read(:key) }
+ :read_multi | [:key_1, :key_2, :key_3] | ->(s) { s.read_multi(:key_1, :key_2, :key_3) }
+ :write_multi | [{ key_1: 1, key_2: 2, key_3: 3 }] | ->(s) { s.write_multi(key_1: 1, key_2: 2, key_3: 3) }
+ :fetch_multi | [:key_1, :key_2, :key_3] | ->(s) { s.fetch_multi(:key_1, :key_2, :key_3) {} }
+ :write | [:key, :value, { option_1: 1 }] | ->(s) { s.write(:key, :value, option_1: 1) }
+ :delete | [:key] | ->(s) { s.delete(:key) }
+ :exist? | [:key, { option_1: 1 }] | ->(s) { s.exist?(:key, option_1: 1) }
+ :delete_matched | [/^key$/, { option_1: 1 }] | ->(s) { s.delete_matched(/^key$/, option_1: 1 ) }
+ :increment | [:key, 1] | ->(s) { s.increment(:key, 1) }
+ :decrement | [:key, 1] | ->(s) { s.decrement(:key, 1) }
+ :cleanup | [] | ->(s) { s.cleanup }
+ :clear | [] | ->(s) { s.clear }
+ end
+
+ with_them do
+ it 'publishes a notification' do
+ event = nil
+
+ begin
+ subscriber = ActiveSupport::Notifications.subscribe("redis.rack_attack") do |*args|
+ event = ActiveSupport::Notifications::Event.new(*args)
+ end
+
+ test_proc.call(subject)
+ ensure
+ ActiveSupport::Notifications.unsubscribe(subscriber) if subscriber
+ end
+
+ expect(event).not_to be_nil
+ expect(event.name).to eq("redis.rack_attack")
+ expect(event.duration).to be_a(Float).and(be > 0.0)
+ expect(event.payload[:operation]).to eql(operation)
+ end
+
+ it 'publishes a notification even if the cache store returns an error' do
+ allow(store).to receive(operation).and_raise('Something went wrong')
+
+ event = nil
+ exception = nil
+
+ begin
+ subscriber = ActiveSupport::Notifications.subscribe("redis.rack_attack") do |*args|
+ event = ActiveSupport::Notifications::Event.new(*args)
+ end
+
+ begin
+ test_proc.call(subject)
+ rescue => e
+ exception = e
+ end
+ ensure
+ ActiveSupport::Notifications.unsubscribe(subscriber) if subscriber
+ end
+
+ expect(event).not_to be_nil
+ expect(event.name).to eq("redis.rack_attack")
+ expect(event.duration).to be_a(Float).and(be > 0.0)
+ expect(event.payload[:operation]).to eql(operation)
+
+ expect(exception).not_to be_nil
+ expect(exception.message).to eql('Something went wrong')
+ end
+
+ it 'delegates to the upstream store' do
+ allow(store).to receive(operation).and_call_original
+
+ if params.empty?
+ expect(store).to receive(operation).with(no_args)
+ else
+ expect(store).to receive(operation).with(*params)
+ end
+
+ test_proc.call(subject)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/rack_attack_spec.rb b/spec/lib/gitlab/rack_attack_spec.rb
index 5748e1e49e5..788d2eac61f 100644
--- a/spec/lib/gitlab/rack_attack_spec.rb
+++ b/spec/lib/gitlab/rack_attack_spec.rb
@@ -6,6 +6,7 @@ RSpec.describe Gitlab::RackAttack, :aggregate_failures do
describe '.configure' do
let(:fake_rack_attack) { class_double("Rack::Attack") }
let(:fake_rack_attack_request) { class_double("Rack::Attack::Request") }
+ let(:fake_cache) { instance_double("Rack::Attack::Cache") }
let(:throttles) do
{
@@ -27,6 +28,8 @@ RSpec.describe Gitlab::RackAttack, :aggregate_failures do
allow(fake_rack_attack).to receive(:track)
allow(fake_rack_attack).to receive(:safelist)
allow(fake_rack_attack).to receive(:blocklist)
+ allow(fake_rack_attack).to receive(:cache).and_return(fake_cache)
+ allow(fake_cache).to receive(:store=)
end
it 'extends the request class' do
diff --git a/spec/lib/gitlab/repository_cache_adapter_spec.rb b/spec/lib/gitlab/repository_cache_adapter_spec.rb
index 4c57665b41f..625dcf11546 100644
--- a/spec/lib/gitlab/repository_cache_adapter_spec.rb
+++ b/spec/lib/gitlab/repository_cache_adapter_spec.rb
@@ -292,12 +292,11 @@ RSpec.describe Gitlab::RepositoryCacheAdapter do
describe '#expire_method_caches' do
it 'expires the caches of the given methods' do
- expect(cache).to receive(:expire).with(:rendered_readme)
expect(cache).to receive(:expire).with(:branch_names)
- expect(redis_set_cache).to receive(:expire).with(:rendered_readme, :branch_names)
- expect(redis_hash_cache).to receive(:delete).with(:rendered_readme, :branch_names)
+ expect(redis_set_cache).to receive(:expire).with(:branch_names)
+ expect(redis_hash_cache).to receive(:delete).with(:branch_names)
- repository.expire_method_caches(%i(rendered_readme branch_names))
+ repository.expire_method_caches(%i(branch_names))
end
it 'does not expire caches for non-existent methods' do
diff --git a/spec/lib/gitlab/search/query_spec.rb b/spec/lib/gitlab/search/query_spec.rb
index dd2f23a7e47..234b683ba1f 100644
--- a/spec/lib/gitlab/search/query_spec.rb
+++ b/spec/lib/gitlab/search/query_spec.rb
@@ -46,4 +46,22 @@ RSpec.describe Gitlab::Search::Query do
expect(subject.filters).to all(include(negated: true))
end
end
+
+ context 'with filter value in quotes' do
+ let(:query) { '"foo bar" name:"my test script.txt"' }
+
+ it 'does not break the filter value in quotes' do
+ expect(subject.term).to eq('"foo bar"')
+ expect(subject.filters[0]).to include(name: :name, negated: false, value: "MY TEST SCRIPT.TXT")
+ end
+ end
+
+ context 'with extra white spaces between the query words' do
+ let(:query) { ' foo = bar name:"my test.txt"' }
+
+ it 'removes the extra whitespace between tokens' do
+ expect(subject.term).to eq('foo = bar')
+ expect(subject.filters[0]).to include(name: :name, negated: false, value: "MY TEST.TXT")
+ end
+ end
end
diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb
index c437b6bcceb..158d472f7ea 100644
--- a/spec/lib/gitlab/search_results_spec.rb
+++ b/spec/lib/gitlab/search_results_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::SearchResults do
include ProjectForksHelper
include SearchHelpers
+ using RSpec::Parameterized::TableSyntax
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, name: 'foo') }
@@ -41,8 +42,6 @@ RSpec.describe Gitlab::SearchResults do
end
describe '#formatted_count' do
- using RSpec::Parameterized::TableSyntax
-
where(:scope, :count_method, :expected) do
'projects' | :limited_projects_count | max_limited_count
'issues' | :limited_issues_count | max_limited_count
@@ -61,8 +60,6 @@ RSpec.describe Gitlab::SearchResults do
end
describe '#highlight_map' do
- using RSpec::Parameterized::TableSyntax
-
where(:scope, :expected) do
'projects' | {}
'issues' | {}
@@ -80,8 +77,6 @@ RSpec.describe Gitlab::SearchResults do
end
describe '#formatted_limited_count' do
- using RSpec::Parameterized::TableSyntax
-
where(:count, :expected) do
23 | '23'
99 | '99'
diff --git a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
index b99a5352717..856ae87c5bf 100644
--- a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
+++ b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
@@ -38,7 +38,8 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do
'pid' => Process.pid,
'created_at' => created_at.to_f,
'enqueued_at' => created_at.to_f,
- 'scheduling_latency_s' => scheduling_latency_s
+ 'scheduling_latency_s' => scheduling_latency_s,
+ 'job_size_bytes' => be > 0
)
end
diff --git a/spec/lib/gitlab/suggestions/commit_message_spec.rb b/spec/lib/gitlab/suggestions/commit_message_spec.rb
index 1411f64f8b7..965960f0c3e 100644
--- a/spec/lib/gitlab/suggestions/commit_message_spec.rb
+++ b/spec/lib/gitlab/suggestions/commit_message_spec.rb
@@ -72,6 +72,17 @@ RSpec.describe Gitlab::Suggestions::CommitMessage do
end
end
+ context 'when a custom commit message is specified' do
+ let(:message) { "i'm a project message. a user's custom message takes precedence over me :(" }
+ let(:custom_message) { "hello there! i'm a cool custom commit message." }
+
+ it 'shows the custom commit message' do
+ expect(Gitlab::Suggestions::CommitMessage
+ .new(user, suggestion_set, custom_message)
+ .message).to eq(custom_message)
+ end
+ end
+
context 'is specified and includes all placeholders' do
let(:message) do
'*** %{branch_name} %{files_count} %{file_paths} %{project_name} %{project_path} %{user_full_name} %{username} %{suggestions_count} ***'
diff --git a/spec/lib/gitlab/template/finders/global_template_finder_spec.rb b/spec/lib/gitlab/template/finders/global_template_finder_spec.rb
index e2751d194d3..38ec28c2b9a 100644
--- a/spec/lib/gitlab/template/finders/global_template_finder_spec.rb
+++ b/spec/lib/gitlab/template/finders/global_template_finder_spec.rb
@@ -15,9 +15,19 @@ RSpec.describe Gitlab::Template::Finders::GlobalTemplateFinder do
FileUtils.rm_rf(base_dir)
end
- subject(:finder) { described_class.new(base_dir, '', { 'General' => '', 'Bar' => 'Bar' }, excluded_patterns: excluded_patterns) }
+ subject(:finder) do
+ described_class.new(base_dir, '',
+ { 'General' => '', 'Bar' => 'Bar' },
+ include_categories_for_file,
+ excluded_patterns: excluded_patterns)
+ end
let(:excluded_patterns) { [] }
+ let(:include_categories_for_file) do
+ {
+ "SAST" => { "Security" => "Security" }
+ }
+ end
describe '.find' do
context 'with a non-prefixed General template' do
@@ -60,6 +70,7 @@ RSpec.describe Gitlab::Template::Finders::GlobalTemplateFinder do
context 'with a prefixed template' do
before do
create_template!('Bar/test-template')
+ create_template!('Security/SAST')
end
it 'finds the template with a prefix' do
@@ -76,6 +87,16 @@ RSpec.describe Gitlab::Template::Finders::GlobalTemplateFinder do
expect { finder.find('../foo') }.to raise_error(/Invalid path/)
end
+ context 'with include_categories_for_file being present' do
+ it 'finds the template with a prefix' do
+ expect(finder.find('SAST')).to be_present
+ end
+
+ it 'does not find any template which is missing in include_categories_for_file' do
+ expect(finder.find('DAST')).to be_nil
+ end
+ end
+
context 'while listed as an exclusion' do
let(:excluded_patterns) { [%r{^Bar/test-template$}] }
diff --git a/spec/lib/gitlab/terraform/state_migration_helper_spec.rb b/spec/lib/gitlab/terraform/state_migration_helper_spec.rb
new file mode 100644
index 00000000000..36c9c060e98
--- /dev/null
+++ b/spec/lib/gitlab/terraform/state_migration_helper_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Terraform::StateMigrationHelper do
+ before do
+ stub_terraform_state_object_storage
+ end
+
+ describe '.migrate_to_remote_storage' do
+ let!(:local_version) { create(:terraform_state_version, file_store: Terraform::StateUploader::Store::LOCAL) }
+
+ subject { described_class.migrate_to_remote_storage }
+
+ it 'migrates remote files to remote storage' do
+ subject
+
+ expect(local_version.reload.file_store).to eq(Terraform::StateUploader::Store::REMOTE)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/tracking/standard_context_spec.rb b/spec/lib/gitlab/tracking/standard_context_spec.rb
index acf7aeb303a..b12a0310dac 100644
--- a/spec/lib/gitlab/tracking/standard_context_spec.rb
+++ b/spec/lib/gitlab/tracking/standard_context_spec.rb
@@ -9,11 +9,37 @@ RSpec.describe Gitlab::Tracking::StandardContext do
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
+ context 'default fields' do
+ context 'environment' do
+ shared_examples 'contains environment' do |expected_environment|
+ it 'contains environment' do
+ expect(snowplow_context.to_json.dig(:data, :environment)).to eq(expected_environment)
+ end
end
+
+ context 'development or test' do
+ include_examples 'contains environment', 'development'
+ end
+
+ context 'staging' do
+ before do
+ allow(Gitlab).to receive(:staging?).and_return(true)
+ end
+
+ include_examples 'contains environment', 'staging'
+ end
+
+ context 'production' do
+ before do
+ allow(Gitlab).to receive(:com_and_canary?).and_return(true)
+ end
+
+ include_examples 'contains environment', 'production'
+ end
+ end
+
+ it 'contains source' do
+ expect(snowplow_context.to_json.dig(:data, :source)).to eq(described_class::GITLAB_RAILS_SOURCE)
end
end
@@ -28,8 +54,8 @@ RSpec.describe Gitlab::Tracking::StandardContext do
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)
+ it 'creates a Snowplow context without namespace and project' do
+ expect(snowplow_context.to_json.dig(:data, :namespace_id)).to be_nil
expect(snowplow_context.to_json.dig(:data, :project_id)).to be_nil
end
end
@@ -37,18 +63,18 @@ RSpec.describe Gitlab::Tracking::StandardContext do
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)
+ it 'creates a Snowplow context without namespace and project' do
+ expect(snowplow_context.to_json.dig(:data, :namespace_id)).to be_nil
+ expect(snowplow_context.to_json.dig(:data, :project_id)).to be_nil
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)
+ it 'creates a Snowplow context without namespace and project' do
+ expect(snowplow_context.to_json.dig(:data, :namespace_id)).to be_nil
+ expect(snowplow_context.to_json.dig(:data, :project_id)).to be_nil
end
end
end
diff --git a/spec/lib/gitlab/url_blocker_spec.rb b/spec/lib/gitlab/url_blocker_spec.rb
index 686382dc262..fa01d4e48df 100644
--- a/spec/lib/gitlab/url_blocker_spec.rb
+++ b/spec/lib/gitlab/url_blocker_spec.rb
@@ -302,36 +302,36 @@ RSpec.describe Gitlab::UrlBlocker, :stub_invalid_dns_only do
it 'does not block urls from private networks' do
local_ips.each do |ip|
stub_domain_resolv(fake_domain, ip) do
- expect(described_class).not_to be_blocked_url("http://#{fake_domain}", url_blocker_attributes)
+ expect(described_class).not_to be_blocked_url("http://#{fake_domain}", **url_blocker_attributes)
end
- expect(described_class).not_to be_blocked_url("http://#{ip}", url_blocker_attributes)
+ expect(described_class).not_to be_blocked_url("http://#{ip}", **url_blocker_attributes)
end
end
it 'allows localhost endpoints' do
- expect(described_class).not_to be_blocked_url('http://0.0.0.0', url_blocker_attributes)
- expect(described_class).not_to be_blocked_url('http://localhost', url_blocker_attributes)
- expect(described_class).not_to be_blocked_url('http://127.0.0.1', url_blocker_attributes)
+ expect(described_class).not_to be_blocked_url('http://0.0.0.0', **url_blocker_attributes)
+ expect(described_class).not_to be_blocked_url('http://localhost', **url_blocker_attributes)
+ expect(described_class).not_to be_blocked_url('http://127.0.0.1', **url_blocker_attributes)
end
it 'allows loopback endpoints' do
- expect(described_class).not_to be_blocked_url('http://127.0.0.2', url_blocker_attributes)
+ expect(described_class).not_to be_blocked_url('http://127.0.0.2', **url_blocker_attributes)
end
it 'allows IPv4 link-local endpoints' do
- expect(described_class).not_to be_blocked_url('http://169.254.169.254', url_blocker_attributes)
- expect(described_class).not_to be_blocked_url('http://169.254.168.100', url_blocker_attributes)
+ expect(described_class).not_to be_blocked_url('http://169.254.169.254', **url_blocker_attributes)
+ expect(described_class).not_to be_blocked_url('http://169.254.168.100', **url_blocker_attributes)
end
it 'allows IPv6 link-local endpoints' do
- expect(described_class).not_to be_blocked_url('http://[0:0:0:0:0:ffff:169.254.169.254]', url_blocker_attributes)
- expect(described_class).not_to be_blocked_url('http://[::ffff:169.254.169.254]', url_blocker_attributes)
- expect(described_class).not_to be_blocked_url('http://[::ffff:a9fe:a9fe]', url_blocker_attributes)
- expect(described_class).not_to be_blocked_url('http://[0:0:0:0:0:ffff:169.254.168.100]', url_blocker_attributes)
- expect(described_class).not_to be_blocked_url('http://[::ffff:169.254.168.100]', url_blocker_attributes)
- expect(described_class).not_to be_blocked_url('http://[::ffff:a9fe:a864]', url_blocker_attributes)
- expect(described_class).not_to be_blocked_url('http://[fe80::c800:eff:fe74:8]', url_blocker_attributes)
+ expect(described_class).not_to be_blocked_url('http://[0:0:0:0:0:ffff:169.254.169.254]', **url_blocker_attributes)
+ expect(described_class).not_to be_blocked_url('http://[::ffff:169.254.169.254]', **url_blocker_attributes)
+ expect(described_class).not_to be_blocked_url('http://[::ffff:a9fe:a9fe]', **url_blocker_attributes)
+ expect(described_class).not_to be_blocked_url('http://[0:0:0:0:0:ffff:169.254.168.100]', **url_blocker_attributes)
+ expect(described_class).not_to be_blocked_url('http://[::ffff:169.254.168.100]', **url_blocker_attributes)
+ expect(described_class).not_to be_blocked_url('http://[::ffff:a9fe:a864]', **url_blocker_attributes)
+ expect(described_class).not_to be_blocked_url('http://[fe80::c800:eff:fe74:8]', **url_blocker_attributes)
end
end
@@ -416,11 +416,11 @@ RSpec.describe Gitlab::UrlBlocker, :stub_invalid_dns_only do
attrs = url_blocker_attributes.merge(dns_rebind_protection: false)
stub_domain_resolv('example.com', '192.168.1.2') do
- expect(described_class).not_to be_blocked_url(url, attrs)
+ expect(described_class).not_to be_blocked_url(url, **attrs)
end
stub_domain_resolv('example.com', '192.168.1.3') do
- expect(described_class).to be_blocked_url(url, attrs)
+ expect(described_class).to be_blocked_url(url, **attrs)
end
end
end
@@ -442,18 +442,18 @@ RSpec.describe Gitlab::UrlBlocker, :stub_invalid_dns_only do
stub_domain_resolv(domain, '192.168.1.1') do
expect(described_class).not_to be_blocked_url("http://#{domain}",
- url_blocker_attributes)
+ **url_blocker_attributes)
end
stub_domain_resolv(subdomain1, '192.168.1.1') do
expect(described_class).not_to be_blocked_url("http://#{subdomain1}",
- url_blocker_attributes)
+ **url_blocker_attributes)
end
# subdomain2 is not part of the allowlist so it should be blocked
stub_domain_resolv(subdomain2, '192.168.1.1') do
expect(described_class).to be_blocked_url("http://#{subdomain2}",
- url_blocker_attributes)
+ **url_blocker_attributes)
end
end
@@ -463,12 +463,12 @@ RSpec.describe Gitlab::UrlBlocker, :stub_invalid_dns_only do
stub_domain_resolv(unicode_domain, '192.168.1.1') do
expect(described_class).not_to be_blocked_url("http://#{unicode_domain}",
- url_blocker_attributes)
+ **url_blocker_attributes)
end
stub_domain_resolv(idna_encoded_domain, '192.168.1.1') do
expect(described_class).not_to be_blocked_url("http://#{idna_encoded_domain}",
- url_blocker_attributes)
+ **url_blocker_attributes)
end
end
@@ -525,7 +525,7 @@ RSpec.describe Gitlab::UrlBlocker, :stub_invalid_dns_only do
it 'allows domain with port when resolved ip has port allowed' do
stub_domain_resolv("www.resolve-domain.com", '127.0.0.1') do
- expect(described_class).not_to be_blocked_url("http://www.resolve-domain.com:2000", url_blocker_attributes)
+ expect(described_class).not_to be_blocked_url("http://www.resolve-domain.com:2000", **url_blocker_attributes)
end
end
end
diff --git a/spec/lib/gitlab/url_blockers/url_allowlist_spec.rb b/spec/lib/gitlab/url_blockers/url_allowlist_spec.rb
index d9e44e9b85c..4c4248b143e 100644
--- a/spec/lib/gitlab/url_blockers/url_allowlist_spec.rb
+++ b/spec/lib/gitlab/url_blockers/url_allowlist_spec.rb
@@ -37,19 +37,19 @@ RSpec.describe Gitlab::UrlBlockers::UrlAllowlist do
let(:allowlist) { ['example.io:3000'] }
it 'returns true if domain and ports present in allowlist' do
- parsed_allowlist = [['example.io', { port: 3000 }]]
+ parsed_allowlist = [['example.io', 3000]]
not_allowed = [
'example.io',
- ['example.io', { port: 3001 }]
+ ['example.io', 3001]
]
aggregate_failures do
- parsed_allowlist.each do |domain_and_port|
- expect(described_class).to be_domain_allowed(*domain_and_port)
+ parsed_allowlist.each do |domain, port|
+ expect(described_class).to be_domain_allowed(domain, port: port)
end
- not_allowed.each do |domain_and_port|
- expect(described_class).not_to be_domain_allowed(*domain_and_port)
+ not_allowed.each do |domain, port|
+ expect(described_class).not_to be_domain_allowed(domain, port: port)
end
end
end
@@ -139,23 +139,23 @@ RSpec.describe Gitlab::UrlBlockers::UrlAllowlist do
it 'returns true if ip and ports present in allowlist' do
parsed_allowlist = [
- ['127.0.0.9', { port: 3000 }],
- ['[2001:db8:85a3:8d3:1319:8a2e:370:7348]', { port: 443 }]
+ ['127.0.0.9', 3000],
+ ['[2001:db8:85a3:8d3:1319:8a2e:370:7348]', 443]
]
not_allowed = [
'127.0.0.9',
- ['127.0.0.9', { port: 3001 }],
+ ['127.0.0.9', 3001],
'[2001:db8:85a3:8d3:1319:8a2e:370:7348]',
- ['[2001:db8:85a3:8d3:1319:8a2e:370:7348]', { port: 3001 }]
+ ['[2001:db8:85a3:8d3:1319:8a2e:370:7348]', 3001]
]
aggregate_failures do
- parsed_allowlist.each do |ip_and_port|
- expect(described_class).to be_ip_allowed(*ip_and_port)
+ parsed_allowlist.each do |ip, port|
+ expect(described_class).to be_ip_allowed(ip, port: port)
end
- not_allowed.each do |ip_and_port|
- expect(described_class).not_to be_ip_allowed(*ip_and_port)
+ not_allowed.each do |ip, port|
+ expect(described_class).not_to be_ip_allowed(ip, port: port)
end
end
end
diff --git a/spec/lib/gitlab/usage/docs/renderer_spec.rb b/spec/lib/gitlab/usage/docs/renderer_spec.rb
new file mode 100644
index 00000000000..e62861cd677
--- /dev/null
+++ b/spec/lib/gitlab/usage/docs/renderer_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Usage::Docs::Renderer do
+ describe 'contents' do
+ let(:dictionary_path) { Gitlab::Usage::Docs::Renderer::DICTIONARY_PATH }
+ let(:items) { Gitlab::Usage::MetricDefinition.definitions }
+
+ it 'generates dictionary for given items' do
+ generated_dictionary = described_class.new(items).contents
+ generated_dictionary_keys = RDoc::Markdown
+ .parse(generated_dictionary)
+ .table_of_contents
+ .select { |metric_doc| metric_doc.level == 2 && !metric_doc.text.start_with?('info:') }
+ .map(&:text)
+
+ expect(generated_dictionary_keys).to match_array(items.keys)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/usage/docs/value_formatter_spec.rb b/spec/lib/gitlab/usage/docs/value_formatter_spec.rb
new file mode 100644
index 00000000000..ceb00867c95
--- /dev/null
+++ b/spec/lib/gitlab/usage/docs/value_formatter_spec.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Usage::Docs::ValueFormatter do
+ describe '.format' do
+ using RSpec::Parameterized::TableSyntax
+ where(:key, :value, :expected_value) do
+ :group | 'growth::product intelligence' | '`growth::product intelligence`'
+ :data_source | 'redis' | 'Redis'
+ :data_source | 'ruby' | 'Ruby'
+ :introduced_by_url | 'http://test.com' | '[Introduced by](http://test.com)'
+ :tier | %w(gold premium) | 'gold, premium'
+ :distribution | %w(ce ee) | 'ce, ee'
+ :key_path | 'key.path' | '**key.path**'
+ :milestone | '13.4' | '13.4'
+ :status | 'data_available' | 'data_available'
+ end
+
+ with_them do
+ subject { described_class.format(key, value) }
+
+ it { is_expected.to eq(expected_value) }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/usage/metric_definition_spec.rb b/spec/lib/gitlab/usage/metric_definition_spec.rb
index e101f837324..bc64bdfbf56 100644
--- a/spec/lib/gitlab/usage/metric_definition_spec.rb
+++ b/spec/lib/gitlab/usage/metric_definition_spec.rb
@@ -5,17 +5,13 @@ 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'
- },
+ key_path: 'uuid',
group: 'group::product analytics',
time_frame: 'none',
data_source: 'database',
@@ -44,12 +40,11 @@ RSpec.describe Gitlab::Usage::MetricDefinition do
using RSpec::Parameterized::TableSyntax
where(:attribute, :value) do
- :name | nil
:description | nil
:value_type | nil
:value_type | 'test'
:status | nil
- :default_generation | nil
+ :key_path | nil
:group | nil
:time_frame | nil
:time_frame | '29d'
@@ -70,6 +65,20 @@ RSpec.describe Gitlab::Usage::MetricDefinition do
described_class.new(path, attributes).validate!
end
+
+ context 'with skip_validation' do
+ it 'raise exception if skip_validation: false' do
+ expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).at_least(:once).with(instance_of(Gitlab::Usage::Metric::InvalidMetricError))
+
+ described_class.new(path, attributes.merge( { skip_validation: false } )).validate!
+ end
+
+ it 'does not raise exception if has skip_validation: true' do
+ expect(Gitlab::ErrorTracking).not_to receive(:track_and_raise_for_dev_exception)
+
+ described_class.new(path, attributes.merge( { skip_validation: true } )).validate!
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/usage/metric_spec.rb b/spec/lib/gitlab/usage/metric_spec.rb
index 40671d980d6..d4a789419a4 100644
--- a/spec/lib/gitlab/usage/metric_spec.rb
+++ b/spec/lib/gitlab/usage/metric_spec.rb
@@ -4,15 +4,15 @@ 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)
+ it 'returns key_path metric definiton' do
+ expect(described_class.new(key_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
+ where(:key_path, :value, :expected_hash) do
'uuid' | nil | { uuid: nil }
'uuid' | '1111' | { uuid: '1111' }
'counts.issues' | nil | { counts: { issues: nil } }
@@ -21,7 +21,7 @@ RSpec.describe Gitlab::Usage::Metric do
end
with_them do
- subject { described_class.new(default_generation_path: default_generation_path, value: value).unflatten_default_path }
+ subject { described_class.new(key_path: key_path, value: value).unflatten_key_path }
it { is_expected.to eq(expected_hash) }
end
diff --git a/spec/lib/gitlab/usage/metrics/aggregates/aggregate_spec.rb b/spec/lib/gitlab/usage/metrics/aggregates/aggregate_spec.rb
new file mode 100644
index 00000000000..a391872c030
--- /dev/null
+++ b/spec/lib/gitlab/usage/metrics/aggregates/aggregate_spec.rb
@@ -0,0 +1,256 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Usage::Metrics::Aggregates::Aggregate, :clean_gitlab_redis_shared_state do
+ let(:entity1) { 'dfb9d2d2-f56c-4c77-8aeb-6cddc4a1f857' }
+ let(:entity2) { '1dd9afb2-a3ee-4de1-8ae3-a405579c8584' }
+ let(:entity3) { '34rfjuuy-ce56-sa35-ds34-dfer567dfrf2' }
+ let(:entity4) { '8b9a2671-2abf-4bec-a682-22f6a8f7bf31' }
+
+ around do |example|
+ # We need to freeze to a reference time
+ # because visits are grouped by the week number in the year
+ # Without freezing the time, the test may behave inconsistently
+ # depending on which day of the week test is run.
+ # Monday 6th of June
+ reference_time = Time.utc(2020, 6, 1)
+ travel_to(reference_time) { example.run }
+ end
+
+ context 'aggregated_metrics_data' do
+ let(:known_events) do
+ [
+ { name: 'event1_slot', redis_slot: "slot", category: 'category1', aggregation: "weekly" },
+ { name: 'event2_slot', redis_slot: "slot", category: 'category2', aggregation: "weekly" },
+ { name: 'event3_slot', redis_slot: "slot", category: 'category3', aggregation: "weekly" },
+ { name: 'event5_slot', redis_slot: "slot", category: 'category4', aggregation: "weekly" },
+ { name: 'event4', category: 'category2', aggregation: "weekly" }
+ ].map(&:with_indifferent_access)
+ end
+
+ before do
+ allow(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:known_events).and_return(known_events)
+ end
+
+ shared_examples 'aggregated_metrics_data' do
+ context 'no aggregated metrics is defined' do
+ it 'returns empty hash' do
+ allow_next_instance_of(described_class) do |instance|
+ allow(instance).to receive(:aggregated_metrics).and_return([])
+ end
+
+ expect(aggregated_metrics_data).to eq({})
+ end
+ end
+
+ context 'there are aggregated metrics defined' do
+ before do
+ allow_next_instance_of(described_class) do |instance|
+ allow(instance).to receive(:aggregated_metrics).and_return(aggregated_metrics)
+ end
+ end
+
+ context 'with AND operator' do
+ let(:aggregated_metrics) do
+ [
+ { name: 'gmau_1', events: %w[event1_slot event2_slot], operator: "AND" },
+ { name: 'gmau_2', events: %w[event1_slot event2_slot event3_slot], operator: "AND" },
+ { name: 'gmau_3', events: %w[event1_slot event2_slot event3_slot event5_slot], operator: "AND" },
+ { name: 'gmau_4', events: %w[event4], operator: "AND" }
+ ].map(&:with_indifferent_access)
+ end
+
+ it 'returns the number of unique events for all known events' do
+ results = {
+ 'gmau_1' => 3,
+ 'gmau_2' => 2,
+ 'gmau_3' => 1,
+ 'gmau_4' => 3
+ }
+
+ expect(aggregated_metrics_data).to eq(results)
+ end
+ end
+
+ context 'with OR operator' do
+ let(:aggregated_metrics) do
+ [
+ { name: 'gmau_1', events: %w[event3_slot event5_slot], operator: "OR" },
+ { name: 'gmau_2', events: %w[event1_slot event2_slot event3_slot event5_slot], operator: "OR" },
+ { name: 'gmau_3', events: %w[event4], operator: "OR" }
+ ].map(&:with_indifferent_access)
+ end
+
+ it 'returns the number of unique events for all known events' do
+ results = {
+ 'gmau_1' => 2,
+ 'gmau_2' => 3,
+ 'gmau_3' => 3
+ }
+
+ expect(aggregated_metrics_data).to eq(results)
+ end
+ end
+
+ context 'hidden behind feature flag' do
+ let(:enabled_feature_flag) { 'test_ff_enabled' }
+ let(:disabled_feature_flag) { 'test_ff_disabled' }
+ let(:aggregated_metrics) do
+ [
+ # represents stable aggregated metrics that has been fully released
+ { name: 'gmau_without_ff', events: %w[event3_slot event5_slot], operator: "OR" },
+ # represents new aggregated metric that is under performance testing on gitlab.com
+ { name: 'gmau_enabled', events: %w[event4], operator: "AND", feature_flag: enabled_feature_flag },
+ # represents aggregated metric that is under development and shouldn't be yet collected even on gitlab.com
+ { name: 'gmau_disabled', events: %w[event4], operator: "AND", feature_flag: disabled_feature_flag }
+ ].map(&:with_indifferent_access)
+ end
+
+ it 'returns the number of unique events for all known events' do
+ skip_feature_flags_yaml_validation
+ stub_feature_flags(enabled_feature_flag => true, disabled_feature_flag => false)
+
+ expect(aggregated_metrics_data).to eq('gmau_without_ff' => 2, 'gmau_enabled' => 3)
+ end
+ end
+ end
+
+ context 'error handling' do
+ context 'development and test environment' do
+ it 'raises error when unknown aggregation operator is used' do
+ allow_next_instance_of(described_class) do |instance|
+ allow(instance).to receive(:aggregated_metrics)
+ .and_return([{ name: 'gmau_1', events: %w[event1_slot], operator: "SUM" }])
+ end
+
+ expect { aggregated_metrics_data }.to raise_error Gitlab::Usage::Metrics::Aggregates::UnknownAggregationOperator
+ end
+
+ it 're raises Gitlab::UsageDataCounters::HLLRedisCounter::EventError' do
+ error = Gitlab::UsageDataCounters::HLLRedisCounter::EventError
+ allow(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:calculate_events_union).and_raise(error)
+
+ allow_next_instance_of(described_class) do |instance|
+ allow(instance).to receive(:aggregated_metrics)
+ .and_return([{ name: 'gmau_1', events: %w[event1_slot], operator: "OR" }])
+ end
+
+ expect { aggregated_metrics_data }.to raise_error error
+ end
+ end
+
+ context 'production' do
+ before do
+ stub_rails_env('production')
+ end
+
+ it 'rescues unknown aggregation operator error' do
+ allow_next_instance_of(described_class) do |instance|
+ allow(instance).to receive(:aggregated_metrics)
+ .and_return([{ name: 'gmau_1', events: %w[event1_slot], operator: "SUM" }])
+ end
+
+ expect(aggregated_metrics_data).to eq('gmau_1' => -1)
+ end
+
+ it 'rescues Gitlab::UsageDataCounters::HLLRedisCounter::EventError' do
+ error = Gitlab::UsageDataCounters::HLLRedisCounter::EventError
+ allow(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:calculate_events_union).and_raise(error)
+
+ allow_next_instance_of(described_class) do |instance|
+ allow(instance).to receive(:aggregated_metrics)
+ .and_return([{ name: 'gmau_1', events: %w[event1_slot], operator: "OR" }])
+ end
+
+ expect(aggregated_metrics_data).to eq('gmau_1' => -1)
+ end
+ end
+ end
+ end
+
+ describe '.aggregated_metrics_weekly_data' do
+ subject(:aggregated_metrics_data) { described_class.new.weekly_data }
+
+ before do
+ Gitlab::UsageDataCounters::HLLRedisCounter.track_event('event1_slot', values: entity1, time: 2.days.ago)
+ Gitlab::UsageDataCounters::HLLRedisCounter.track_event('event1_slot', values: entity2, time: 2.days.ago)
+ Gitlab::UsageDataCounters::HLLRedisCounter.track_event('event1_slot', values: entity3, time: 2.days.ago)
+ Gitlab::UsageDataCounters::HLLRedisCounter.track_event('event2_slot', values: entity1, time: 2.days.ago)
+ Gitlab::UsageDataCounters::HLLRedisCounter.track_event('event2_slot', values: entity2, time: 3.days.ago)
+ Gitlab::UsageDataCounters::HLLRedisCounter.track_event('event2_slot', values: entity3, time: 3.days.ago)
+ Gitlab::UsageDataCounters::HLLRedisCounter.track_event('event3_slot', values: entity1, time: 3.days.ago)
+ Gitlab::UsageDataCounters::HLLRedisCounter.track_event('event3_slot', values: entity2, time: 3.days.ago)
+ Gitlab::UsageDataCounters::HLLRedisCounter.track_event('event5_slot', values: entity2, time: 3.days.ago)
+
+ # events out of time scope
+ Gitlab::UsageDataCounters::HLLRedisCounter.track_event('event2_slot', values: entity3, time: 8.days.ago)
+
+ # events in different slots
+ Gitlab::UsageDataCounters::HLLRedisCounter.track_event('event4', values: entity1, time: 2.days.ago)
+ Gitlab::UsageDataCounters::HLLRedisCounter.track_event('event4', values: entity2, time: 2.days.ago)
+ Gitlab::UsageDataCounters::HLLRedisCounter.track_event('event4', values: entity4, time: 2.days.ago)
+ end
+
+ it_behaves_like 'aggregated_metrics_data'
+ end
+
+ describe '.aggregated_metrics_monthly_data' do
+ subject(:aggregated_metrics_data) { described_class.new.monthly_data }
+
+ it_behaves_like 'aggregated_metrics_data' do
+ before do
+ Gitlab::UsageDataCounters::HLLRedisCounter.track_event('event1_slot', values: entity1, time: 2.days.ago)
+ Gitlab::UsageDataCounters::HLLRedisCounter.track_event('event1_slot', values: entity2, time: 2.days.ago)
+ Gitlab::UsageDataCounters::HLLRedisCounter.track_event('event1_slot', values: entity3, time: 2.days.ago)
+ Gitlab::UsageDataCounters::HLLRedisCounter.track_event('event2_slot', values: entity1, time: 2.days.ago)
+ Gitlab::UsageDataCounters::HLLRedisCounter.track_event('event2_slot', values: entity2, time: 3.days.ago)
+ Gitlab::UsageDataCounters::HLLRedisCounter.track_event('event2_slot', values: entity3, time: 3.days.ago)
+ Gitlab::UsageDataCounters::HLLRedisCounter.track_event('event3_slot', values: entity1, time: 3.days.ago)
+ Gitlab::UsageDataCounters::HLLRedisCounter.track_event('event3_slot', values: entity2, time: 10.days.ago)
+ Gitlab::UsageDataCounters::HLLRedisCounter.track_event('event5_slot', values: entity2, time: 4.weeks.ago.advance(days: 1))
+
+ # events out of time scope
+ Gitlab::UsageDataCounters::HLLRedisCounter.track_event('event5_slot', values: entity1, time: 4.weeks.ago.advance(days: -1))
+
+ # events in different slots
+ Gitlab::UsageDataCounters::HLLRedisCounter.track_event('event4', values: entity1, time: 2.days.ago)
+ Gitlab::UsageDataCounters::HLLRedisCounter.track_event('event4', values: entity2, time: 2.days.ago)
+ Gitlab::UsageDataCounters::HLLRedisCounter.track_event('event4', values: entity4, time: 2.days.ago)
+ end
+ end
+
+ context 'Redis calls' do
+ let(:aggregated_metrics) do
+ [
+ { name: 'gmau_3', events: %w[event1_slot event2_slot event3_slot event5_slot], operator: "AND" }
+ ].map(&:with_indifferent_access)
+ end
+
+ it 'caches intermediate operations' do
+ allow_next_instance_of(described_class) do |instance|
+ allow(instance).to receive(:aggregated_metrics).and_return(aggregated_metrics)
+ end
+
+ aggregated_metrics[0][:events].each do |event|
+ expect(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:calculate_events_union)
+ .with(event_names: event, start_date: 4.weeks.ago.to_date, end_date: Date.current)
+ .once
+ .and_return(0)
+ end
+
+ 2.upto(4) do |subset_size|
+ aggregated_metrics[0][:events].combination(subset_size).each do |events|
+ expect(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:calculate_events_union)
+ .with(event_names: events, start_date: 4.weeks.ago.to_date, end_date: Date.current)
+ .once
+ .and_return(0)
+ end
+ end
+
+ aggregated_metrics_data
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/usage_data_counters/aggregated_metrics_spec.rb b/spec/lib/gitlab/usage_data_counters/aggregated_metrics_spec.rb
index c0deb2aa00c..6ca7039b3de 100644
--- a/spec/lib/gitlab/usage_data_counters/aggregated_metrics_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/aggregated_metrics_spec.rb
@@ -17,7 +17,7 @@ RSpec.describe 'aggregated metrics' do
Gitlab::UsageDataCounters::HLLRedisCounter.known_events
end
- Gitlab::UsageDataCounters::HLLRedisCounter.aggregated_metrics.tap do |aggregated_metrics|
+ Gitlab::Usage::Metrics::Aggregates::Aggregate.new.send(:aggregated_metrics).tap do |aggregated_metrics|
it 'all events has unique name' do
event_names = aggregated_metrics&.map { |event| event[:name] }
@@ -37,7 +37,7 @@ RSpec.describe 'aggregated metrics' do
end
it "uses allowed aggregation operators" do
- expect(Gitlab::UsageDataCounters::HLLRedisCounter::ALLOWED_METRICS_AGGREGATIONS).to include aggregate[:operator]
+ expect(Gitlab::Usage::Metrics::Aggregates::ALLOWED_METRICS_AGGREGATIONS).to include aggregate[:operator]
end
it "uses events from the same Redis slot" do
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 b8eddc0ca7f..f0b8ce6c2fb 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
@@ -39,7 +39,9 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
'snippets',
'code_review',
'terraform',
- 'ci_templates'
+ 'ci_templates',
+ 'quickactions',
+ 'pipeline_authoring'
)
end
end
@@ -425,182 +427,59 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
end
end
- context 'aggregated_metrics_data' do
+ describe '.calculate_events_union' do
+ let(:time_range) { { start_date: 7.days.ago, end_date: DateTime.current } }
let(:known_events) do
[
{ name: 'event1_slot', redis_slot: "slot", category: 'category1', aggregation: "weekly" },
{ name: 'event2_slot', redis_slot: "slot", category: 'category2', aggregation: "weekly" },
{ name: 'event3_slot', redis_slot: "slot", category: 'category3', aggregation: "weekly" },
- { name: 'event5_slot', redis_slot: "slot", category: 'category4', aggregation: "weekly" },
+ { name: 'event5_slot', redis_slot: "slot", category: 'category4', aggregation: "daily" },
{ name: 'event4', category: 'category2', aggregation: "weekly" }
].map(&:with_indifferent_access)
end
before do
allow(described_class).to receive(:known_events).and_return(known_events)
- end
-
- shared_examples 'aggregated_metrics_data' do
- context 'no aggregated metrics is defined' do
- it 'returns empty hash' do
- allow(described_class).to receive(:aggregated_metrics).and_return([])
- expect(aggregated_metrics_data).to eq({})
- end
- end
-
- context 'there are aggregated metrics defined' do
- before do
- allow(described_class).to receive(:aggregated_metrics).and_return(aggregated_metrics)
- end
-
- context 'with AND operator' do
- let(:aggregated_metrics) do
- [
- { name: 'gmau_1', events: %w[event1_slot event2_slot], operator: "AND" },
- { name: 'gmau_2', events: %w[event1_slot event2_slot event3_slot], operator: "AND" },
- { name: 'gmau_3', events: %w[event1_slot event2_slot event3_slot event5_slot], operator: "AND" },
- { name: 'gmau_4', events: %w[event4], operator: "AND" }
- ].map(&:with_indifferent_access)
- end
-
- it 'returns the number of unique events for all known events' do
- results = {
- 'gmau_1' => 3,
- 'gmau_2' => 2,
- 'gmau_3' => 1,
- 'gmau_4' => 3
- }
-
- expect(aggregated_metrics_data).to eq(results)
- end
- end
-
- context 'with OR operator' do
- let(:aggregated_metrics) do
- [
- { name: 'gmau_1', events: %w[event3_slot event5_slot], operator: "OR" },
- { name: 'gmau_2', events: %w[event1_slot event2_slot event3_slot event5_slot], operator: "OR" },
- { name: 'gmau_3', events: %w[event4], operator: "OR" }
- ].map(&:with_indifferent_access)
- end
-
- it 'returns the number of unique events for all known events' do
- results = {
- 'gmau_1' => 2,
- 'gmau_2' => 3,
- 'gmau_3' => 3
- }
-
- expect(aggregated_metrics_data).to eq(results)
- end
- end
-
- context 'hidden behind feature flag' do
- let(:enabled_feature_flag) { 'test_ff_enabled' }
- let(:disabled_feature_flag) { 'test_ff_disabled' }
- let(:aggregated_metrics) do
- [
- # represents stable aggregated metrics that has been fully released
- { name: 'gmau_without_ff', events: %w[event3_slot event5_slot], operator: "OR" },
- # represents new aggregated metric that is under performance testing on gitlab.com
- { name: 'gmau_enabled', events: %w[event4], operator: "AND", feature_flag: enabled_feature_flag },
- # represents aggregated metric that is under development and shouldn't be yet collected even on gitlab.com
- { name: 'gmau_disabled', events: %w[event4], operator: "AND", feature_flag: disabled_feature_flag }
- ].map(&:with_indifferent_access)
- end
-
- it 'returns the number of unique events for all known events' do
- skip_feature_flags_yaml_validation
- stub_feature_flags(enabled_feature_flag => true, disabled_feature_flag => false)
+ 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('event2_slot', values: entity4, time: 8.days.ago)
- expect(aggregated_metrics_data).to eq('gmau_without_ff' => 2, 'gmau_enabled' => 3)
- end
- end
- end
+ # events in different slots
+ described_class.track_event('event4', values: entity1, time: 2.days.ago)
+ described_class.track_event('event4', values: entity2, time: 2.days.ago)
end
- describe '.aggregated_metrics_weekly_data' do
- subject(:aggregated_metrics_data) { described_class.aggregated_metrics_weekly_data }
-
- before do
- 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('event2_slot', values: entity3, time: 8.days.ago)
-
- # events in different slots
- 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'
+ it 'calculates union of given events', :aggregate_failure do
+ expect(described_class.calculate_events_union(**time_range.merge(event_names: %w[event4]))).to eq 2
+ expect(described_class.calculate_events_union(**time_range.merge(event_names: %w[event1_slot event2_slot event3_slot]))).to eq 3
end
- describe '.aggregated_metrics_monthly_data' do
- subject(:aggregated_metrics_data) { described_class.aggregated_metrics_monthly_data }
-
- it_behaves_like 'aggregated_metrics_data' do
- before do
- 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('event5_slot', values: entity1, time: 4.weeks.ago.advance(days: -1))
-
- # events in different slots
- 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
-
- context 'Redis calls' do
- let(:aggregated_metrics) do
- [
- { name: 'gmau_3', events: %w[event1_slot event2_slot event3_slot event5_slot], operator: "AND" }
- ].map(&:with_indifferent_access)
- end
-
- let(:known_events) do
- [
- { name: 'event1_slot', redis_slot: "slot", category: 'category1', aggregation: "weekly" },
- { name: 'event2_slot', redis_slot: "slot", category: 'category2', aggregation: "weekly" },
- { name: 'event3_slot', redis_slot: "slot", category: 'category3', aggregation: "weekly" },
- { name: 'event5_slot', redis_slot: "slot", category: 'category4', aggregation: "weekly" }
- ].map(&:with_indifferent_access)
- end
-
- it 'caches intermediate operations' do
- allow(described_class).to receive(:known_events).and_return(known_events)
- allow(described_class).to receive(:aggregated_metrics).and_return(aggregated_metrics)
+ it 'validates and raise exception if events has mismatched slot or aggregation', :aggregate_failure do
+ expect { described_class.calculate_events_union(**time_range.merge(event_names: %w[event1_slot event4])) }.to raise_error described_class::SlotMismatch
+ expect { described_class.calculate_events_union(**time_range.merge(event_names: %w[event5_slot event3_slot])) }.to raise_error described_class::AggregationMismatch
+ end
+ end
- 4.downto(1) do |subset_size|
- known_events.combination(subset_size).each do |events|
- keys = described_class.send(:weekly_redis_keys, events: events, start_date: 4.weeks.ago.to_date, end_date: Date.current)
- expect(Gitlab::Redis::HLL).to receive(:count).with(keys: keys).once.and_return(0)
- end
- end
+ describe '.weekly_time_range' do
+ it 'return hash with weekly time range boundaries' do
+ expect(described_class.weekly_time_range).to eq(start_date: 7.days.ago.to_date, end_date: Date.current)
+ end
+ end
- subject
- end
- end
+ describe '.monthly_time_range' do
+ it 'return hash with monthly time range boundaries' do
+ expect(described_class.monthly_time_range).to eq(start_date: 4.weeks.ago.to_date, end_date: Date.current)
end
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
index c7b208cfb31..509ba43ef32 100644
--- a/spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb
@@ -73,6 +73,22 @@ RSpec.describe Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter, :cl
end
end
+ describe '.track_resolve_thread_action' do
+ subject { described_class.track_resolve_thread_action(user: user) }
+
+ it_behaves_like 'a tracked merge request unique event' do
+ let(:action) { described_class::MR_RESOLVE_THREAD_ACTION }
+ end
+ end
+
+ describe '.track_unresolve_thread_action' do
+ subject { described_class.track_unresolve_thread_action(user: user) }
+
+ it_behaves_like 'a tracked merge request unique event' do
+ let(:action) { described_class::MR_UNRESOLVE_THREAD_ACTION }
+ end
+ end
+
describe '.track_create_comment_action' do
subject { described_class.track_create_comment_action(note: note) }
@@ -148,4 +164,36 @@ RSpec.describe Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter, :cl
let(:action) { described_class::MR_PUBLISH_REVIEW_ACTION }
end
end
+
+ describe '.track_add_suggestion_action' do
+ subject { described_class.track_add_suggestion_action(user: user) }
+
+ it_behaves_like 'a tracked merge request unique event' do
+ let(:action) { described_class::MR_ADD_SUGGESTION_ACTION }
+ end
+ end
+
+ describe '.track_apply_suggestion_action' do
+ subject { described_class.track_apply_suggestion_action(user: user) }
+
+ it_behaves_like 'a tracked merge request unique event' do
+ let(:action) { described_class::MR_APPLY_SUGGESTION_ACTION }
+ end
+ end
+
+ describe '.track_users_assigned_to_mr' do
+ subject { described_class.track_users_assigned_to_mr(users: [user]) }
+
+ it_behaves_like 'a tracked merge request unique event' do
+ let(:action) { described_class::MR_ASSIGNED_USERS_ACTION }
+ end
+ end
+
+ describe '.track_users_review_requested' do
+ subject { described_class.track_users_review_requested(users: [user]) }
+
+ it_behaves_like 'a tracked merge request unique event' do
+ let(:action) { described_class::MR_REVIEW_REQUESTED_USERS_ACTION }
+ end
+ end
end
diff --git a/spec/lib/gitlab/usage_data_counters/quick_action_activity_unique_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/quick_action_activity_unique_counter_spec.rb
new file mode 100644
index 00000000000..d4c423f57fe
--- /dev/null
+++ b/spec/lib/gitlab/usage_data_counters/quick_action_activity_unique_counter_spec.rb
@@ -0,0 +1,163 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::UsageDataCounters::QuickActionActivityUniqueCounter, :clean_gitlab_redis_shared_state do
+ let(:user) { build(:user, id: 1) }
+ let(:note) { build(:note, author: user) }
+ let(:args) { nil }
+
+ shared_examples_for 'a tracked quick action 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
+
+ subject { described_class.track_unique_action(quickaction_name, args: args, user: user) }
+
+ describe '.track_unique_action' do
+ let(:quickaction_name) { 'approve' }
+
+ it_behaves_like 'a tracked quick action unique event' do
+ let(:action) { 'i_quickactions_approve' }
+ end
+ end
+
+ context 'tracking assigns' do
+ let(:quickaction_name) { 'assign' }
+
+ context 'single assignee' do
+ let(:args) { '@one' }
+
+ it_behaves_like 'a tracked quick action unique event' do
+ let(:action) { 'i_quickactions_assign_single' }
+ end
+ end
+
+ context 'multiple assignees' do
+ let(:args) { '@one @two' }
+
+ it_behaves_like 'a tracked quick action unique event' do
+ let(:action) { 'i_quickactions_assign_multiple' }
+ end
+ end
+
+ context 'assigning "me"' do
+ let(:args) { 'me' }
+
+ it_behaves_like 'a tracked quick action unique event' do
+ let(:action) { 'i_quickactions_assign_self' }
+ end
+ end
+
+ context 'assigning a reviewer' do
+ let(:quickaction_name) { 'assign_reviewer' }
+
+ it_behaves_like 'a tracked quick action unique event' do
+ let(:action) { 'i_quickactions_assign_reviewer' }
+ end
+ end
+
+ context 'assigning a reviewer with request review alias' do
+ let(:quickaction_name) { 'request_review' }
+
+ it_behaves_like 'a tracked quick action unique event' do
+ let(:action) { 'i_quickactions_assign_reviewer' }
+ end
+ end
+ end
+
+ context 'tracking copy_metadata' do
+ let(:quickaction_name) { 'copy_metadata' }
+
+ context 'for issues' do
+ let(:args) { '#123' }
+
+ it_behaves_like 'a tracked quick action unique event' do
+ let(:action) { 'i_quickactions_copy_metadata_issue' }
+ end
+ end
+
+ context 'for merge requests' do
+ let(:args) { '!123' }
+
+ it_behaves_like 'a tracked quick action unique event' do
+ let(:action) { 'i_quickactions_copy_metadata_merge_request' }
+ end
+ end
+ end
+
+ context 'tracking spend' do
+ let(:quickaction_name) { 'spend' }
+
+ context 'adding time' do
+ let(:args) { '1d' }
+
+ it_behaves_like 'a tracked quick action unique event' do
+ let(:action) { 'i_quickactions_spend_add' }
+ end
+ end
+
+ context 'removing time' do
+ let(:args) { '-1d' }
+
+ it_behaves_like 'a tracked quick action unique event' do
+ let(:action) { 'i_quickactions_spend_subtract' }
+ end
+ end
+ end
+
+ context 'tracking unassign' do
+ let(:quickaction_name) { 'unassign' }
+
+ context 'unassigning everyone' do
+ it_behaves_like 'a tracked quick action unique event' do
+ let(:action) { 'i_quickactions_unassign_all' }
+ end
+ end
+
+ context 'unassigning specific users' do
+ let(:args) { '@hello' }
+
+ it_behaves_like 'a tracked quick action unique event' do
+ let(:action) { 'i_quickactions_unassign_specific' }
+ end
+ end
+ end
+
+ context 'tracking unlabel' do
+ context 'called as unlabel' do
+ let(:quickaction_name) { 'unlabel' }
+
+ context 'removing all labels' do
+ it_behaves_like 'a tracked quick action unique event' do
+ let(:action) { 'i_quickactions_unlabel_all' }
+ end
+ end
+
+ context 'removing specific labels' do
+ let(:args) { '~wow' }
+
+ it_behaves_like 'a tracked quick action unique event' do
+ let(:action) { 'i_quickactions_unlabel_specific' }
+ end
+ end
+ end
+
+ context 'called as remove_label' do
+ let(:quickaction_name) { 'remove_label' }
+
+ it_behaves_like 'a tracked quick action unique event' do
+ let(:action) { 'i_quickactions_unlabel_all' }
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index fd02521622c..602f6640d72 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -228,11 +228,32 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
)
end
- it 'includes imports usage data' do
+ it 'includes import gmau usage data' do
for_defined_days_back do
user = create(:user)
+ group = create(:group)
+ group.add_owner(user)
+
+ create(:project, import_type: :github, creator_id: user.id)
+ create(:jira_import_state, :finished, project: create(:project, creator_id: user.id))
+ create(:issue_csv_import, user: user)
+ create(:group_import_state, group: group, user: user)
create(:bulk_import, user: user)
+ end
+
+ expect(described_class.usage_activity_by_stage_manage({})).to include(
+ unique_users_all_imports: 10
+ )
+
+ expect(described_class.usage_activity_by_stage_manage(described_class.last_28_days_time_period)).to include(
+ unique_users_all_imports: 5
+ )
+ end
+
+ it 'includes imports usage data' do
+ for_defined_days_back do
+ user = create(:user)
%w(gitlab_project gitlab github bitbucket bitbucket_server gitea git manifest fogbugz phabricator).each do |type|
create(:project, import_type: type, creator_id: user.id)
@@ -242,72 +263,113 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
create(:jira_import_state, :finished, project: jira_project)
create(:issue_csv_import, user: user)
+
+ group = create(:group)
+ group.add_owner(user)
+ create(:group_import_state, group: group, user: user)
+
+ bulk_import = create(:bulk_import, user: user)
+ create(:bulk_import_entity, :group_entity, bulk_import: bulk_import)
+ create(:bulk_import_entity, :project_entity, bulk_import: bulk_import)
end
expect(described_class.usage_activity_by_stage_manage({})).to include(
{
bulk_imports: {
- gitlab: 2
+ gitlab_v1: 2,
+ gitlab: Gitlab::UsageData::DEPRECATED_VALUE
},
- projects_imported: {
- total: 2,
- gitlab_project: 2,
- gitlab: 2,
- github: 2,
+ project_imports: {
bitbucket: 2,
bitbucket_server: 2,
- gitea: 2,
git: 2,
+ gitea: 2,
+ github: 2,
+ gitlab: 2,
+ gitlab_migration: 2,
+ gitlab_project: 2,
manifest: 2
},
- issues_imported: {
+ issue_imports: {
jira: 2,
fogbugz: 2,
phabricator: 2,
csv: 2
- }
+ },
+ group_imports: {
+ group_import: 2,
+ gitlab_migration: 2
+ },
+ projects_imported: {
+ total: Gitlab::UsageData::DEPRECATED_VALUE,
+ gitlab_project: Gitlab::UsageData::DEPRECATED_VALUE,
+ gitlab: Gitlab::UsageData::DEPRECATED_VALUE,
+ github: Gitlab::UsageData::DEPRECATED_VALUE,
+ bitbucket: Gitlab::UsageData::DEPRECATED_VALUE,
+ bitbucket_server: Gitlab::UsageData::DEPRECATED_VALUE,
+ gitea: Gitlab::UsageData::DEPRECATED_VALUE,
+ git: Gitlab::UsageData::DEPRECATED_VALUE,
+ manifest: Gitlab::UsageData::DEPRECATED_VALUE
+ },
+ issues_imported: {
+ jira: Gitlab::UsageData::DEPRECATED_VALUE,
+ fogbugz: Gitlab::UsageData::DEPRECATED_VALUE,
+ phabricator: Gitlab::UsageData::DEPRECATED_VALUE,
+ csv: Gitlab::UsageData::DEPRECATED_VALUE
+ },
+ groups_imported: Gitlab::UsageData::DEPRECATED_VALUE
}
)
expect(described_class.usage_activity_by_stage_manage(described_class.last_28_days_time_period)).to include(
{
bulk_imports: {
- gitlab: 1
+ gitlab_v1: 1,
+ gitlab: Gitlab::UsageData::DEPRECATED_VALUE
},
- projects_imported: {
- total: 1,
- gitlab_project: 1,
- gitlab: 1,
- github: 1,
+ project_imports: {
bitbucket: 1,
bitbucket_server: 1,
- gitea: 1,
git: 1,
+ gitea: 1,
+ github: 1,
+ gitlab: 1,
+ gitlab_migration: 1,
+ gitlab_project: 1,
manifest: 1
},
- issues_imported: {
+ issue_imports: {
jira: 1,
fogbugz: 1,
phabricator: 1,
csv: 1
- }
+ },
+ group_imports: {
+ group_import: 1,
+ gitlab_migration: 1
+ },
+ projects_imported: {
+ total: Gitlab::UsageData::DEPRECATED_VALUE,
+ gitlab_project: Gitlab::UsageData::DEPRECATED_VALUE,
+ gitlab: Gitlab::UsageData::DEPRECATED_VALUE,
+ github: Gitlab::UsageData::DEPRECATED_VALUE,
+ bitbucket: Gitlab::UsageData::DEPRECATED_VALUE,
+ bitbucket_server: Gitlab::UsageData::DEPRECATED_VALUE,
+ gitea: Gitlab::UsageData::DEPRECATED_VALUE,
+ git: Gitlab::UsageData::DEPRECATED_VALUE,
+ manifest: Gitlab::UsageData::DEPRECATED_VALUE
+ },
+ issues_imported: {
+ jira: Gitlab::UsageData::DEPRECATED_VALUE,
+ fogbugz: Gitlab::UsageData::DEPRECATED_VALUE,
+ phabricator: Gitlab::UsageData::DEPRECATED_VALUE,
+ csv: Gitlab::UsageData::DEPRECATED_VALUE
+ },
+ groups_imported: Gitlab::UsageData::DEPRECATED_VALUE
+
}
)
end
- it 'includes group imports usage data' do
- for_defined_days_back do
- user = create(:user)
- group = create(:group)
- group.add_owner(user)
- create(:group_import_state, group: group, user: user)
- end
-
- expect(described_class.usage_activity_by_stage_manage({}))
- .to include(groups_imported: 2)
- expect(described_class.usage_activity_by_stage_manage(described_class.last_28_days_time_period))
- .to include(groups_imported: 1)
- end
-
def omniauth_providers
[
OpenStruct.new(name: 'google_oauth2'),
@@ -1262,7 +1324,9 @@ 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 ci_secrets_management incident_management_alerts snippets terraform] }
+ let(:ineligible_total_categories) do
+ %w[source_code ci_secrets_management incident_management_alerts snippets terraform pipeline_authoring]
+ end
it 'has all known_events' do
expect(subject).to have_key(:redis_hll_counters)
@@ -1286,8 +1350,10 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
describe '.aggregated_metrics_weekly' do
subject(:aggregated_metrics_payload) { described_class.aggregated_metrics_weekly }
- it 'uses ::Gitlab::UsageDataCounters::HLLRedisCounter#aggregated_metrics_data', :aggregate_failures do
- expect(::Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:aggregated_metrics_weekly_data).and_return(global_search_gmau: 123)
+ it 'uses ::Gitlab::Usage::Metrics::Aggregates::Aggregate#weekly_data', :aggregate_failures do
+ expect_next_instance_of(::Gitlab::Usage::Metrics::Aggregates::Aggregate) do |instance|
+ expect(instance).to receive(:weekly_data).and_return(global_search_gmau: 123)
+ end
expect(aggregated_metrics_payload).to eq(aggregated_metrics: { global_search_gmau: 123 })
end
end
@@ -1295,8 +1361,10 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
describe '.aggregated_metrics_monthly' do
subject(:aggregated_metrics_payload) { described_class.aggregated_metrics_monthly }
- it 'uses ::Gitlab::UsageDataCounters::HLLRedisCounter#aggregated_metrics_data', :aggregate_failures do
- expect(::Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:aggregated_metrics_monthly_data).and_return(global_search_gmau: 123)
+ it 'uses ::Gitlab::Usage::Metrics::Aggregates::Aggregate#monthly_data', :aggregate_failures do
+ expect_next_instance_of(::Gitlab::Usage::Metrics::Aggregates::Aggregate) do |instance|
+ expect(instance).to receive(:monthly_data).and_return(global_search_gmau: 123)
+ end
expect(aggregated_metrics_payload).to eq(aggregated_metrics: { global_search_gmau: 123 })
end
end
diff --git a/spec/lib/gitlab/utils/markdown_spec.rb b/spec/lib/gitlab/utils/markdown_spec.rb
index 93d91f7ed90..acc5bd47c8c 100644
--- a/spec/lib/gitlab/utils/markdown_spec.rb
+++ b/spec/lib/gitlab/utils/markdown_spec.rb
@@ -53,33 +53,23 @@ RSpec.describe Gitlab::Utils::Markdown do
end
context 'when string has a product suffix' do
- let(:string) { 'My Header (ULTIMATE)' }
-
- it 'ignores a product suffix' do
- is_expected.to eq 'my-header'
- end
-
- context 'with only modifier' do
- let(:string) { 'My Header (STARTER ONLY)' }
-
- it 'ignores a product suffix' do
- is_expected.to eq 'my-header'
- end
- end
-
- context 'with "*" around a product suffix' do
- let(:string) { 'My Header **(STARTER)**' }
-
- it 'ignores a product suffix' do
- is_expected.to eq 'my-header'
- end
- end
-
- context 'with "*" around a product suffix and only modifier' do
- let(:string) { 'My Header **(STARTER ONLY)**' }
-
- it 'ignores a product suffix' do
- is_expected.to eq 'my-header'
+ %w[CORE STARTER PREMIUM ULTIMATE FREE BRONZE SILVER GOLD].each do |tier|
+ ['', ' ONLY', ' SELF', ' SASS'].each do |modifier|
+ context "#{tier}#{modifier}" do
+ let(:string) { "My Header (#{tier}#{modifier})" }
+
+ it 'ignores a product suffix' do
+ is_expected.to eq 'my-header'
+ end
+
+ context 'with "*" around a product suffix' do
+ let(:string) { "My Header **(#{tier}#{modifier})**" }
+
+ it 'ignores a product suffix' do
+ is_expected.to eq 'my-header'
+ end
+ end
+ end
end
end
end
diff --git a/spec/lib/gitlab/utils/override_spec.rb b/spec/lib/gitlab/utils/override_spec.rb
index 7ba7392df0f..a5e53c1dfc1 100644
--- a/spec/lib/gitlab/utils/override_spec.rb
+++ b/spec/lib/gitlab/utils/override_spec.rb
@@ -2,6 +2,9 @@
require 'fast_spec_helper'
+# Patching ActiveSupport::Concern
+require_relative '../../../../config/initializers/0_as_concern'
+
RSpec.describe Gitlab::Utils::Override do
let(:base) do
Struct.new(:good) do
@@ -164,6 +167,70 @@ RSpec.describe Gitlab::Utils::Override do
it_behaves_like 'checking as intended, nothing was overridden'
end
+
+ context 'when ActiveSupport::Concern and class_methods are used' do
+ # We need to give module names before using Override
+ let(:base) { stub_const('Base', Module.new) }
+ let(:extension) { stub_const('Extension', Module.new) }
+
+ def define_base(method_name:)
+ base.module_eval do
+ extend ActiveSupport::Concern
+
+ class_methods do
+ define_method(method_name) do
+ :f
+ end
+ end
+ end
+ end
+
+ def define_extension(method_name:)
+ extension.module_eval do
+ extend ActiveSupport::Concern
+
+ class_methods do
+ extend Gitlab::Utils::Override
+
+ override method_name
+ define_method(method_name) do
+ :g
+ end
+ end
+ end
+ end
+
+ context 'when it is defining a overriding method' do
+ before do
+ define_base(method_name: :f)
+ define_extension(method_name: :f)
+
+ base.prepend(extension)
+ end
+
+ it 'verifies' do
+ expect(base.f).to eq(:g)
+
+ described_class.verify!
+ end
+ end
+
+ context 'when it is not defining a overriding method' do
+ before do
+ define_base(method_name: :f)
+ define_extension(method_name: :g)
+
+ base.prepend(extension)
+ end
+
+ it 'raises NotImplementedError' do
+ expect(base.f).to eq(:f)
+
+ expect { described_class.verify! }
+ .to raise_error(NotImplementedError)
+ end
+ end
+ end
end
context 'when STATIC_VERIFICATION is not set' do
diff --git a/spec/lib/gitlab/utils/usage_data_spec.rb b/spec/lib/gitlab/utils/usage_data_spec.rb
index dfc381d0ef2..27248d1d95a 100644
--- a/spec/lib/gitlab/utils/usage_data_spec.rb
+++ b/spec/lib/gitlab/utils/usage_data_spec.rb
@@ -58,6 +58,16 @@ RSpec.describe Gitlab::Utils::UsageData do
expect(described_class.estimate_batch_distinct_count(relation, 'column')).to eq(5)
end
+ it 'yield provided block with PostgresHll::Buckets' do
+ buckets = Gitlab::Database::PostgresHll::Buckets.new
+
+ allow_next_instance_of(Gitlab::Database::PostgresHll::BatchDistinctCounter) do |instance|
+ allow(instance).to receive(:execute).and_return(buckets)
+ end
+
+ expect { |block| described_class.estimate_batch_distinct_count(relation, 'column', &block) }.to yield_with_args(buckets)
+ end
+
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
@@ -362,4 +372,97 @@ RSpec.describe Gitlab::Utils::UsageData do
end
end
end
+
+ describe '#save_aggregated_metrics', :clean_gitlab_redis_shared_state do
+ let(:timestamp) { Time.current.to_i }
+ let(:time_period) { { created_at: 7.days.ago..Date.current } }
+ let(:metric_name) { 'test_metric' }
+ let(:method_params) do
+ {
+ metric_name: metric_name,
+ time_period: time_period,
+ recorded_at_timestamp: timestamp,
+ data: data
+ }
+ end
+
+ context 'with compatible data argument' do
+ let(:data) { ::Gitlab::Database::PostgresHll::Buckets.new(141 => 1, 56 => 1) }
+
+ it 'persists serialized data in Redis' do
+ time_period_name = 'weekly'
+
+ expect(described_class).to receive(:time_period_to_human_name).with(time_period).and_return(time_period_name)
+ Gitlab::Redis::SharedState.with do |redis|
+ expect(redis).to receive(:set).with("#{metric_name}_#{time_period_name}-#{timestamp}", '{"141":1,"56":1}', ex: 80.hours)
+ end
+
+ described_class.save_aggregated_metrics(**method_params)
+ end
+
+ context 'error handling' do
+ before do
+ allow(Gitlab::Redis::SharedState).to receive(:with).and_raise(::Redis::CommandError)
+ end
+
+ it 'rescues and reraise ::Redis::CommandError for development and test environments' do
+ expect { described_class.save_aggregated_metrics(**method_params) }.to raise_error ::Redis::CommandError
+ end
+
+ context 'for environment different than development' do
+ before do
+ stub_rails_env('production')
+ end
+
+ it 'rescues ::Redis::CommandError' do
+ expect { described_class.save_aggregated_metrics(**method_params) }.not_to raise_error
+ end
+ end
+ end
+ end
+
+ context 'with incompatible data argument' do
+ let(:data) { 1 }
+
+ context 'for environment different than development' do
+ before do
+ stub_rails_env('production')
+ end
+
+ it 'does not persist data in Redis' do
+ Gitlab::Redis::SharedState.with do |redis|
+ expect(redis).not_to receive(:set)
+ end
+
+ described_class.save_aggregated_metrics(**method_params)
+ end
+ end
+
+ it 'raises error for development environment' do
+ expect { described_class.save_aggregated_metrics(**method_params) }.to raise_error /Unsupported data type/
+ end
+ end
+ end
+
+ describe '#time_period_to_human_name' do
+ it 'translates empty time period as all_time' do
+ expect(described_class.time_period_to_human_name({})).to eql 'all_time'
+ end
+
+ it 'translates time period not longer than 7 days as weekly', :aggregate_failures do
+ days_6_time_period = 6.days.ago..Date.current
+ days_7_time_period = 7.days.ago..Date.current
+
+ expect(described_class.time_period_to_human_name(column_name: days_6_time_period)).to eql 'weekly'
+ expect(described_class.time_period_to_human_name(column_name: days_7_time_period)).to eql 'weekly'
+ end
+
+ it 'translates time period longer than 7 days as monthly', :aggregate_failures do
+ days_8_time_period = 8.days.ago..Date.current
+ days_31_time_period = 31.days.ago..Date.current
+
+ expect(described_class.time_period_to_human_name(column_name: days_8_time_period)).to eql 'monthly'
+ expect(described_class.time_period_to_human_name(column_name: days_31_time_period)).to eql 'monthly'
+ end
+ end
end
diff --git a/spec/lib/gitlab/utils_spec.rb b/spec/lib/gitlab/utils_spec.rb
index 1052d4cbacc..665eebdfd9e 100644
--- a/spec/lib/gitlab/utils_spec.rb
+++ b/spec/lib/gitlab/utils_spec.rb
@@ -116,8 +116,6 @@ RSpec.describe Gitlab::Utils do
end
describe '.ms_to_round_sec' do
- using RSpec::Parameterized::TableSyntax
-
where(:original, :expected) do
1999.8999 | 1.9999
12384 | 12.384
@@ -169,8 +167,6 @@ RSpec.describe Gitlab::Utils do
end
describe '.remove_line_breaks' do
- using RSpec::Parameterized::TableSyntax
-
where(:original, :expected) do
"foo\nbar\nbaz" | "foobarbaz"
"foo\r\nbar\r\nbaz" | "foobarbaz"
@@ -281,8 +277,6 @@ RSpec.describe Gitlab::Utils do
end
describe '.append_path' do
- using RSpec::Parameterized::TableSyntax
-
where(:host, :path, :result) do
'http://test/' | '/foo/bar' | 'http://test/foo/bar'
'http://test/' | '//foo/bar' | 'http://test/foo/bar'
@@ -393,8 +387,6 @@ RSpec.describe Gitlab::Utils do
end
describe ".safe_downcase!" do
- using RSpec::Parameterized::TableSyntax
-
where(:str, :result) do
"test".freeze | "test"
"Test".freeze | "test"