summaryrefslogtreecommitdiff
path: root/spec/migrations
diff options
context:
space:
mode:
Diffstat (limited to 'spec/migrations')
-rw-r--r--spec/migrations/20210818185845_backfill_projects_with_coverage_spec.rb71
-rw-r--r--spec/migrations/20220107064845_populate_vulnerability_reads_spec.rb2
-rw-r--r--spec/migrations/20220921144258_remove_orphan_group_token_users_spec.rb9
-rw-r--r--spec/migrations/20221018050323_add_objective_and_keyresult_to_work_item_types_spec.rb66
-rw-r--r--spec/migrations/20221018062308_schedule_backfill_project_namespace_details_spec.rb37
-rw-r--r--spec/migrations/20221018193635_ensure_task_note_renaming_background_migration_finished_spec.rb91
-rw-r--r--spec/migrations/20221021145820_create_routing_table_for_builds_metadata_v2_spec.rb36
-rw-r--r--spec/migrations/20221025043930_change_default_value_on_password_last_changed_at_to_user_details_spec.rb37
-rw-r--r--spec/migrations/20221028022627_add_index_on_password_last_changed_at_to_user_details_spec.rb16
-rw-r--r--spec/migrations/20221101032521_add_default_preferred_language_to_application_settings_spec.rb27
-rw-r--r--spec/migrations/20221101032600_add_text_limit_to_default_preferred_language_on_application_settings_spec.rb29
-rw-r--r--spec/migrations/20221102090940_create_next_ci_partitions_record_spec.rb63
-rw-r--r--spec/migrations/20221102090943_create_second_partition_for_builds_metadata_spec.rb61
-rw-r--r--spec/migrations/cleanup_vulnerability_state_transitions_with_same_from_state_to_state_spec.rb49
-rw-r--r--spec/migrations/delete_migrate_shared_vulnerability_scanners_spec.rb69
-rw-r--r--spec/migrations/finalize_invalid_member_cleanup_spec.rb72
-rw-r--r--spec/migrations/queue_backfill_user_details_fields_spec.rb24
-rw-r--r--spec/migrations/queue_populate_projects_star_count_spec.rb24
-rw-r--r--spec/migrations/recount_epic_cache_counts_spec.rb32
-rw-r--r--spec/migrations/reschedule_migrate_shared_vulnerability_scanners_spec.rb41
-rw-r--r--spec/migrations/sanitize_confidential_note_todos_spec.rb33
-rw-r--r--spec/migrations/schedule_migrate_shared_vulnerability_scanners_spec.rb59
-rw-r--r--spec/migrations/set_email_confirmation_setting_from_send_user_confirmation_email_setting_spec.rb41
-rw-r--r--spec/migrations/sync_new_amount_used_for_ci_namespace_monthly_usages_spec.rb42
-rw-r--r--spec/migrations/sync_new_amount_used_for_ci_project_monthly_usages_spec.rb42
25 files changed, 936 insertions, 137 deletions
diff --git a/spec/migrations/20210818185845_backfill_projects_with_coverage_spec.rb b/spec/migrations/20210818185845_backfill_projects_with_coverage_spec.rb
deleted file mode 100644
index 13a6aa5413e..00000000000
--- a/spec/migrations/20210818185845_backfill_projects_with_coverage_spec.rb
+++ /dev/null
@@ -1,71 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-require_migration!
-
-RSpec.describe BackfillProjectsWithCoverage, :suppress_gitlab_schemas_validate_connection do
- let(:projects) { table(:projects) }
- let(:ci_pipelines) { table(:ci_pipelines) }
- let(:ci_daily_build_group_report_results) { table(:ci_daily_build_group_report_results) }
- let(:group) { table(:namespaces).create!(name: 'user', path: 'user') }
- let(:project_1) { projects.create!(namespace_id: group.id) }
- let(:project_2) { projects.create!(namespace_id: group.id) }
- let(:pipeline_1) { ci_pipelines.create!(project_id: project_1.id) }
- let(:pipeline_2) { ci_pipelines.create!(project_id: project_2.id) }
- let(:pipeline_3) { ci_pipelines.create!(project_id: project_2.id) }
-
- describe '#up' do
- before do
- stub_const("#{described_class}::BATCH_SIZE", 2)
- stub_const("#{described_class}::SUB_BATCH_SIZE", 1)
-
- ci_daily_build_group_report_results.create!(
- id: 1,
- project_id: project_1.id,
- date: 3.days.ago,
- last_pipeline_id: pipeline_1.id,
- ref_path: 'main',
- group_name: 'rspec',
- data: { coverage: 95.0 },
- default_branch: true,
- group_id: group.id
- )
-
- ci_daily_build_group_report_results.create!(
- id: 2,
- project_id: project_2.id,
- date: 2.days.ago,
- last_pipeline_id: pipeline_2.id,
- ref_path: 'main',
- group_name: 'rspec',
- data: { coverage: 95.0 },
- default_branch: true,
- group_id: group.id
- )
-
- ci_daily_build_group_report_results.create!(
- id: 3,
- project_id: project_2.id,
- date: 1.day.ago,
- last_pipeline_id: pipeline_3.id,
- ref_path: 'test_branch',
- group_name: 'rspec',
- data: { coverage: 95.0 },
- default_branch: false,
- group_id: group.id
- )
- end
-
- it 'schedules BackfillProjectsWithCoverage background jobs', :aggregate_failures do
- Sidekiq::Testing.fake! do
- freeze_time do
- migrate!
-
- expect(described_class::MIGRATION).to be_scheduled_delayed_migration(2.minutes, 1, 2, 1)
- expect(described_class::MIGRATION).to be_scheduled_delayed_migration(4.minutes, 3, 3, 1)
- expect(BackgroundMigrationWorker.jobs.size).to eq(2)
- end
- end
- end
- end
-end
diff --git a/spec/migrations/20220107064845_populate_vulnerability_reads_spec.rb b/spec/migrations/20220107064845_populate_vulnerability_reads_spec.rb
index ece971a50c9..063a51227dd 100644
--- a/spec/migrations/20220107064845_populate_vulnerability_reads_spec.rb
+++ b/spec/migrations/20220107064845_populate_vulnerability_reads_spec.rb
@@ -29,7 +29,7 @@ RSpec.describe PopulateVulnerabilityReads, :migration do
project_id: project.id,
external_type: 'uuid-v5',
external_id: 'uuid-v5',
- fingerprint: Digest::SHA1.hexdigest("#{vulnerability.id}"),
+ fingerprint: Digest::SHA1.hexdigest(vulnerability.id.to_s),
name: 'Identifier for UUIDv5')
create_finding!(
diff --git a/spec/migrations/20220921144258_remove_orphan_group_token_users_spec.rb b/spec/migrations/20220921144258_remove_orphan_group_token_users_spec.rb
index 614044657ec..174cfda1a46 100644
--- a/spec/migrations/20220921144258_remove_orphan_group_token_users_spec.rb
+++ b/spec/migrations/20220921144258_remove_orphan_group_token_users_spec.rb
@@ -34,11 +34,7 @@ RSpec.describe RemoveOrphanGroupTokenUsers, :migration, :sidekiq_inline do
let(:members) { table(:members) }
let(:namespaces) { table(:namespaces) }
- before do
- stub_feature_flags(user_destroy_with_limited_execution_time_worker: false)
- end
-
- it 'removes orphan project bot and its tokens', :aggregate_failures do
+ it 'initiates orphan project bot removal', :aggregate_failures do
expect(DeleteUserWorker)
.to receive(:perform_async)
.with(orphan_bot.id, orphan_bot.id, skip_authorization: true)
@@ -46,7 +42,8 @@ RSpec.describe RemoveOrphanGroupTokenUsers, :migration, :sidekiq_inline do
migrate!
- expect(users.count).to eq 2
+ expect(Users::GhostUserMigration.where(user: orphan_bot)).to be_exists
+ expect(users.count).to eq 3
expect(personal_access_tokens.count).to eq 2
expect(personal_access_tokens.find_by(user_id: orphan_bot.id)).to eq nil
end
diff --git a/spec/migrations/20221018050323_add_objective_and_keyresult_to_work_item_types_spec.rb b/spec/migrations/20221018050323_add_objective_and_keyresult_to_work_item_types_spec.rb
new file mode 100644
index 00000000000..4de897802b9
--- /dev/null
+++ b/spec/migrations/20221018050323_add_objective_and_keyresult_to_work_item_types_spec.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe AddObjectiveAndKeyresultToWorkItemTypes, :migration do
+ include MigrationHelpers::WorkItemTypesHelper
+
+ let_it_be(:work_item_types) { table(:work_item_types) }
+
+ let(:base_types) do
+ {
+ issue: 0,
+ incident: 1,
+ test_case: 2,
+ requirement: 3,
+ task: 4,
+ objective: 5,
+ key_result: 6
+ }
+ end
+
+ after(:all) do
+ # Make sure base types are recreated after running the migration
+ # because migration specs are not run in a transaction
+ reset_work_item_types
+ end
+
+ it 'skips creating both objective & keyresult type record if it already exists' do
+ reset_db_state_prior_to_migration
+ work_item_types.find_or_create_by!(name: 'Key Result', namespace_id: nil, base_type: base_types[:key_result],
+ icon_name: 'issue-type-keyresult')
+ work_item_types.find_or_create_by!(name: 'Objective', namespace_id: nil, base_type: base_types[:objective],
+ icon_name: 'issue-type-objective')
+
+ expect do
+ migrate!
+ end.to not_change(work_item_types, :count)
+ end
+
+ it 'adds both objective & keyresult to base work item types' do
+ reset_db_state_prior_to_migration
+
+ expect do
+ migrate!
+ end.to change(work_item_types, :count).from(5).to(7)
+
+ expect(work_item_types.all.pluck(:base_type)).to include(base_types[:objective])
+ expect(work_item_types.all.pluck(:base_type)).to include(base_types[:key_result])
+ end
+
+ def reset_db_state_prior_to_migration
+ # Database needs to be in a similar state as when this migration was created
+ work_item_types.delete_all
+ work_item_types.find_or_create_by!(name: 'Issue', namespace_id: nil, base_type: base_types[:issue],
+ icon_name: 'issue-type-issue')
+ work_item_types.find_or_create_by!(name: 'Incident', namespace_id: nil, base_type: base_types[:incident],
+ icon_name: 'issue-type-incident')
+ work_item_types.find_or_create_by!(name: 'Test Case', namespace_id: nil, base_type: base_types[:test_case],
+ icon_name: 'issue-type-test-case')
+ work_item_types.find_or_create_by!(name: 'Requirement', namespace_id: nil, base_type: base_types[:requirement],
+ icon_name: 'issue-type-requirements')
+ work_item_types.find_or_create_by!(name: 'Task', namespace_id: nil, base_type: base_types[:task],
+ icon_name: 'issue-type-task')
+ end
+end
diff --git a/spec/migrations/20221018062308_schedule_backfill_project_namespace_details_spec.rb b/spec/migrations/20221018062308_schedule_backfill_project_namespace_details_spec.rb
new file mode 100644
index 00000000000..4dd6d5757ce
--- /dev/null
+++ b/spec/migrations/20221018062308_schedule_backfill_project_namespace_details_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe ScheduleBackfillProjectNamespaceDetails, schema: 20221018062308 do
+ context 'when on gitlab.com' do
+ let_it_be(:background_migration) { described_class::MIGRATION }
+ let_it_be(:migration) { described_class.new }
+
+ before do
+ migration.up
+ end
+
+ describe '#up' do
+ it 'schedules background jobs for each batch of projects' do
+ expect(background_migration).to(
+ have_scheduled_batched_migration(
+ table_name: :projects,
+ column_name: :id,
+ interval: described_class::INTERVAL,
+ batch_size: described_class::BATCH_SIZE,
+ sub_batch_size: described_class::SUB_BATCH_SIZE
+ )
+ )
+ end
+ end
+
+ describe '#down' do
+ it 'deletes all batched migration records' do
+ migration.down
+
+ expect(described_class::MIGRATION).not_to have_scheduled_batched_migration
+ end
+ end
+ end
+end
diff --git a/spec/migrations/20221018193635_ensure_task_note_renaming_background_migration_finished_spec.rb b/spec/migrations/20221018193635_ensure_task_note_renaming_background_migration_finished_spec.rb
new file mode 100644
index 00000000000..ea95c34674e
--- /dev/null
+++ b/spec/migrations/20221018193635_ensure_task_note_renaming_background_migration_finished_spec.rb
@@ -0,0 +1,91 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe EnsureTaskNoteRenamingBackgroundMigrationFinished, :migration do
+ let(:batched_migrations) { table(:batched_background_migrations) }
+ let(:batch_failed_status) { 2 }
+ let(:batch_finalized_status) { 3 }
+
+ let_it_be(:migration) { described_class::MIGRATION }
+
+ describe '#up' do
+ shared_examples 'finalizes the migration' do
+ it 'finalizes the migration' do
+ expect do
+ migrate!
+
+ task_renaming_migration.reload
+ failed_job.reload
+ end.to change(task_renaming_migration, :status).from(task_renaming_migration.status).to(3).and(
+ change(failed_job, :status).from(batch_failed_status).to(batch_finalized_status)
+ )
+ end
+ end
+
+ context 'when migration is missing' do
+ it 'warns migration not found' do
+ expect(Gitlab::AppLogger)
+ .to receive(:warn).with(/Could not find batched background migration for the given configuration:/)
+
+ migrate!
+ end
+ end
+
+ context 'with migration present' do
+ let!(:task_renaming_migration) do
+ batched_migrations.create!(
+ job_class_name: 'RenameTaskSystemNoteToChecklistItem',
+ table_name: :system_note_metadata,
+ column_name: :id,
+ job_arguments: [],
+ interval: 2.minutes,
+ min_value: 1,
+ max_value: 2,
+ batch_size: 1000,
+ sub_batch_size: 200,
+ gitlab_schema: :gitlab_main,
+ status: 3 # finished
+ )
+ end
+
+ context 'when migration finished successfully' do
+ it 'does not raise exception' do
+ expect { migrate! }.not_to raise_error
+ end
+ end
+
+ context 'with different migration statuses', :redis do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:status, :description) do
+ 0 | 'paused'
+ 1 | 'active'
+ 4 | 'failed'
+ 5 | 'finalizing'
+ end
+
+ with_them do
+ let!(:failed_job) do
+ table(:batched_background_migration_jobs).create!(
+ batched_background_migration_id: task_renaming_migration.id,
+ status: batch_failed_status,
+ min_value: 1,
+ max_value: 10,
+ attempts: 2,
+ batch_size: 100,
+ sub_batch_size: 10
+ )
+ end
+
+ before do
+ task_renaming_migration.update!(status: status)
+ end
+
+ it_behaves_like 'finalizes the migration'
+ end
+ end
+ end
+ end
+end
diff --git a/spec/migrations/20221021145820_create_routing_table_for_builds_metadata_v2_spec.rb b/spec/migrations/20221021145820_create_routing_table_for_builds_metadata_v2_spec.rb
new file mode 100644
index 00000000000..48a00df430d
--- /dev/null
+++ b/spec/migrations/20221021145820_create_routing_table_for_builds_metadata_v2_spec.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe CreateRoutingTableForBuildsMetadataV2, :migration do
+ let_it_be(:migration) { described_class.new }
+
+ describe '#up' do
+ context 'when the table is already partitioned' do
+ before do
+ # `convert_table_to_first_list_partition` checks if it's being executed
+ # inside a transaction, but we're using transactional fixtures here so we
+ # need to tell it that it's not inside a transaction.
+ # We toggle the behavior depending on how many transactions we have open
+ # instead of just returning `false` because the migration could have the
+ # DDL transaction enabled.
+ #
+ open_transactions = ActiveRecord::Base.connection.open_transactions
+ allow(migration).to receive(:transaction_open?) do
+ ActiveRecord::Base.connection.open_transactions > open_transactions
+ end
+
+ migration.convert_table_to_first_list_partition(
+ table_name: :ci_builds_metadata,
+ partitioning_column: :partition_id,
+ parent_table_name: :p_ci_builds_metadata,
+ initial_partitioning_value: 100)
+ end
+
+ it 'skips the migration' do
+ expect { migrate! }.not_to raise_error
+ end
+ end
+ end
+end
diff --git a/spec/migrations/20221025043930_change_default_value_on_password_last_changed_at_to_user_details_spec.rb b/spec/migrations/20221025043930_change_default_value_on_password_last_changed_at_to_user_details_spec.rb
new file mode 100644
index 00000000000..4d6f06eb146
--- /dev/null
+++ b/spec/migrations/20221025043930_change_default_value_on_password_last_changed_at_to_user_details_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe ChangeDefaultValueOnPasswordLastChangedAtToUserDetails, :migration do
+ let(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') }
+ let(:users) { table(:users) }
+ let(:user_details) { table(:user_details) }
+
+ it 'correctly migrates up and down' do
+ user = create_user!(email: '1234@abc')
+ user_details.create!(user_id: user.id, provisioned_by_group_id: namespace.id)
+
+ expect(UserDetail.find_by(user_id: user.id).password_last_changed_at).to be_nil
+
+ migrate!
+
+ user = create_user!(email: 'abc@1234')
+ user_details.create!(user_id: user.id, provisioned_by_group_id: namespace.id)
+
+ expect(UserDetail.find_by(user_id: user.id).password_last_changed_at).not_to be_nil
+ end
+
+ private
+
+ def create_user!(name: "Example User", email: "user@example.com", user_type: nil)
+ users.create!(
+ name: name,
+ email: email,
+ username: name,
+ projects_limit: 0,
+ user_type: user_type,
+ confirmed_at: Time.current
+ )
+ end
+end
diff --git a/spec/migrations/20221028022627_add_index_on_password_last_changed_at_to_user_details_spec.rb b/spec/migrations/20221028022627_add_index_on_password_last_changed_at_to_user_details_spec.rb
new file mode 100644
index 00000000000..5f8467f9307
--- /dev/null
+++ b/spec/migrations/20221028022627_add_index_on_password_last_changed_at_to_user_details_spec.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe AddIndexOnPasswordLastChangedAtToUserDetails, :migration do
+ let(:index_name) { 'index_user_details_on_password_last_changed_at' }
+
+ it 'correctly migrates up and down' do
+ expect(subject).not_to be_index_exists_by_name(:user_details, index_name)
+
+ migrate!
+
+ expect(subject).to be_index_exists_by_name(:user_details, index_name)
+ end
+end
diff --git a/spec/migrations/20221101032521_add_default_preferred_language_to_application_settings_spec.rb b/spec/migrations/20221101032521_add_default_preferred_language_to_application_settings_spec.rb
new file mode 100644
index 00000000000..3ae4287f3c4
--- /dev/null
+++ b/spec/migrations/20221101032521_add_default_preferred_language_to_application_settings_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+require_migration!
+
+RSpec.describe AddDefaultPreferredLanguageToApplicationSettings do
+ let(:application_setting) { table(:application_settings).create! }
+
+ describe "#up" do
+ it 'allows to read default_preferred_language field' do
+ migrate!
+
+ expect(application_setting.attributes.keys).to include('default_preferred_language')
+ expect(application_setting.default_preferred_language).to eq 'en'
+ end
+ end
+
+ describe "#down" do
+ it 'deletes default_preferred_language field' do
+ migrate!
+ schema_migrate_down!
+
+ expect(application_setting.attributes.keys).not_to include('default_preferred_language')
+ end
+ end
+end
diff --git a/spec/migrations/20221101032600_add_text_limit_to_default_preferred_language_on_application_settings_spec.rb b/spec/migrations/20221101032600_add_text_limit_to_default_preferred_language_on_application_settings_spec.rb
new file mode 100644
index 00000000000..e0370e48db6
--- /dev/null
+++ b/spec/migrations/20221101032600_add_text_limit_to_default_preferred_language_on_application_settings_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+require_migration!
+
+RSpec.describe AddTextLimitToDefaultPreferredLanguageOnApplicationSettings do
+ let(:application_setting) { table(:application_settings).create! }
+ let(:too_long_text) { SecureRandom.alphanumeric(described_class::MAXIMUM_LIMIT + 1) }
+
+ subject { application_setting.update_column(:default_preferred_language, too_long_text) }
+
+ describe "#up" do
+ it 'adds text limit to default_preferred_language' do
+ migrate!
+
+ expect { subject }.to raise_error ActiveRecord::StatementInvalid
+ end
+ end
+
+ describe "#down" do
+ it 'deletes text limit to default_preferred_language' do
+ migrate!
+ schema_migrate_down!
+
+ expect { subject }.not_to raise_error
+ end
+ end
+end
diff --git a/spec/migrations/20221102090940_create_next_ci_partitions_record_spec.rb b/spec/migrations/20221102090940_create_next_ci_partitions_record_spec.rb
new file mode 100644
index 00000000000..c55e4bcfba7
--- /dev/null
+++ b/spec/migrations/20221102090940_create_next_ci_partitions_record_spec.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe CreateNextCiPartitionsRecord, migration: :gitlab_ci do
+ let(:migration) { described_class.new }
+ let(:partitions) { table(:ci_partitions) }
+
+ describe '#up' do
+ context 'when on sass' do
+ before do
+ allow(Gitlab).to receive(:com?).and_return(true)
+ end
+
+ it 'creates next partitions record and resets the sequence' do
+ expect { migrate! }
+ .to change { partitions.where(id: 101).any? }
+ .from(false).to(true)
+
+ expect { partitions.create! }.not_to raise_error
+ end
+ end
+
+ context 'when self-managed' do
+ before do
+ allow(Gitlab).to receive(:com?).and_return(false)
+ end
+
+ it 'does not create records' do
+ expect { migrate! }.not_to change(partitions, :count)
+ end
+ end
+ end
+
+ describe '#down' do
+ context 'when on sass' do
+ before do
+ allow(Gitlab).to receive(:com?).and_return(true)
+ end
+
+ it 'removes the record' do
+ migrate!
+
+ expect { migration.down }
+ .to change { partitions.where(id: 101).any? }
+ .from(true).to(false)
+ end
+ end
+
+ context 'when self-managed' do
+ before do
+ allow(Gitlab).to receive(:com?).and_return(true, false)
+ end
+
+ it 'does not remove the record' do
+ expect { migrate! }.to change(partitions, :count).by(1)
+
+ expect { migration.down }.not_to change(partitions, :count)
+ end
+ end
+ end
+end
diff --git a/spec/migrations/20221102090943_create_second_partition_for_builds_metadata_spec.rb b/spec/migrations/20221102090943_create_second_partition_for_builds_metadata_spec.rb
new file mode 100644
index 00000000000..99754d609ed
--- /dev/null
+++ b/spec/migrations/20221102090943_create_second_partition_for_builds_metadata_spec.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe CreateSecondPartitionForBuildsMetadata, :migration do
+ let(:migration) { described_class.new }
+ let(:partitions) { table(:ci_partitions) }
+
+ describe '#up' do
+ context 'when on sass' do
+ before do
+ allow(Gitlab).to receive(:com?).and_return(true)
+ end
+
+ it 'creates a new partition' do
+ expect { migrate! }.to change { partitions_count }.by(1)
+ end
+ end
+
+ context 'when self-managed' do
+ before do
+ allow(Gitlab).to receive(:com?).and_return(false)
+ end
+
+ it 'does not create the partition' do
+ expect { migrate! }.not_to change { partitions_count }
+ end
+ end
+ end
+
+ describe '#down' do
+ context 'when on sass' do
+ before do
+ allow(Gitlab).to receive(:com?).and_return(true)
+ end
+
+ it 'removes the partition' do
+ migrate!
+
+ expect { migration.down }.to change { partitions_count }.by(-1)
+ end
+ end
+
+ context 'when self-managed' do
+ before do
+ allow(Gitlab).to receive(:com?).and_return(false)
+ end
+
+ it 'does not change the partitions count' do
+ migrate!
+
+ expect { migration.down }.not_to change { partitions_count }
+ end
+ end
+ end
+
+ def partitions_count
+ Gitlab::Database::PostgresPartition.for_parent_table(:p_ci_builds_metadata).size
+ end
+end
diff --git a/spec/migrations/cleanup_vulnerability_state_transitions_with_same_from_state_to_state_spec.rb b/spec/migrations/cleanup_vulnerability_state_transitions_with_same_from_state_to_state_spec.rb
new file mode 100644
index 00000000000..92ece81ffc8
--- /dev/null
+++ b/spec/migrations/cleanup_vulnerability_state_transitions_with_same_from_state_to_state_spec.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe CleanupVulnerabilityStateTransitionsWithSameFromStateToState, :migration do
+ let_it_be(:namespace) { table(:namespaces).create!(name: 'namespace', type: 'Group', path: 'namespace') }
+ let_it_be(:user) { table(:users).create!(email: 'author@example.com', username: 'author', projects_limit: 10) }
+ let_it_be(:project) do
+ table(:projects).create!(
+ path: 'project',
+ namespace_id: namespace.id,
+ project_namespace_id: namespace.id
+ )
+ end
+
+ let_it_be(:vulnerability) do
+ table(:vulnerabilities).create!(
+ project_id: project.id,
+ author_id: user.id,
+ title: 'test',
+ severity: 7,
+ confidence: 7,
+ report_type: 0
+ )
+ end
+
+ let_it_be(:state_transitions) { table(:vulnerability_state_transitions) }
+
+ let!(:state_transition_with_no_state_change) do
+ state_transitions.create!(
+ vulnerability_id: vulnerability.id,
+ from_state: 2,
+ to_state: 2
+ )
+ end
+
+ let!(:state_transition_with_state_change) do
+ state_transitions.create!(
+ vulnerability_id: vulnerability.id,
+ from_state: 1,
+ to_state: 2
+ )
+ end
+
+ it 'deletes state transitions with no state change' do
+ expect { migrate! }.to change(state_transitions, :count).from(2).to(1)
+ end
+end
diff --git a/spec/migrations/delete_migrate_shared_vulnerability_scanners_spec.rb b/spec/migrations/delete_migrate_shared_vulnerability_scanners_spec.rb
new file mode 100644
index 00000000000..259b175cd19
--- /dev/null
+++ b/spec/migrations/delete_migrate_shared_vulnerability_scanners_spec.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+require_migration!
+
+RSpec.describe DeleteMigrateSharedVulnerabilityScanners, :migration do
+ let(:batched_background_migrations) { table(:batched_background_migrations) }
+ let(:batched_background_migration_jobs) { table(:batched_background_migration_jobs) }
+
+ let(:migration) do
+ batched_background_migrations.create!(created_at: Time.zone.now,
+ updated_at: Time.zone.now,
+ min_value: 1,
+ max_value: 1,
+ batch_size: described_class::BATCH_SIZE,
+ sub_batch_size: 100,
+ interval: 300,
+ status: 3,
+ job_class_name: described_class::MIGRATION,
+ batch_class_name: "PrimaryKeyBatchingStrategy",
+ table_name: described_class::TABLE_NAME,
+ column_name: described_class::BATCH_COLUMN,
+ job_arguments: [],
+ pause_ms: 100,
+ max_batch_size: 1000,
+ gitlab_schema: "gitlab_main")
+ end
+
+ let(:jobs) do
+ Array.new(10) do
+ batched_background_migration_jobs.create!(batched_background_migration_id: migration.id,
+ created_at: Time.zone.now,
+ updated_at: Time.zone.now,
+ min_value: 1,
+ max_value: 1,
+ batch_size: 1,
+ sub_batch_size: 1,
+ status: 0,
+ attempts: 0,
+ metrics: {},
+ pause_ms: 100)
+ end
+ end
+
+ describe "#up" do
+ it "deletes jobs" do
+ expect { migrate! }.to change(batched_background_migration_jobs, :count).from(jobs.count).to(0)
+ end
+
+ it "deletes the migration" do
+ expect { migrate! }.to change { batched_background_migrations.find_by(id: migration.id) }.from(migration).to(nil)
+ end
+
+ context "when background migration does not exist" do
+ before do
+ migration.destroy!
+ end
+
+ it "does not delete jobs" do
+ expect { migrate! }.not_to change(batched_background_migration_jobs, :count)
+ end
+
+ it "does not delete the migration" do
+ expect { migrate! }.not_to change { batched_background_migrations.find_by(id: migration.id) }
+ end
+ end
+ end
+end
diff --git a/spec/migrations/finalize_invalid_member_cleanup_spec.rb b/spec/migrations/finalize_invalid_member_cleanup_spec.rb
new file mode 100644
index 00000000000..a29a89c2396
--- /dev/null
+++ b/spec/migrations/finalize_invalid_member_cleanup_spec.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe FinalizeInvalidMemberCleanup, :migration do
+ let(:batched_migrations) { table(:batched_background_migrations) }
+
+ let_it_be(:migration) { described_class::MIGRATION }
+
+ describe '#up' do
+ shared_examples 'finalizes the migration' do
+ it 'finalizes the migration' do
+ allow_next_instance_of(Gitlab::Database::BackgroundMigration::BatchedMigrationRunner) do |runner|
+ expect(runner).to receive(:finalize).with('DestroyInvalidMembers', :members, :id, [])
+ end
+ end
+ end
+
+ context 'when migration is missing' do
+ it 'warns migration not found' do
+ expect(Gitlab::AppLogger)
+ .to receive(:warn).with(/Could not find batched background migration for the given configuration:/)
+
+ migrate!
+ end
+ end
+
+ context 'with migration present' do
+ let!(:destroy_invalid_member_migration) do
+ batched_migrations.create!(
+ job_class_name: 'DestroyInvalidMembers',
+ table_name: :members,
+ column_name: :id,
+ job_arguments: [],
+ interval: 2.minutes,
+ min_value: 1,
+ max_value: 2,
+ batch_size: 1000,
+ sub_batch_size: 200,
+ gitlab_schema: :gitlab_main,
+ status: 3 # finished
+ )
+ end
+
+ context 'when migration finished successfully' do
+ it 'does not raise exception' do
+ expect { migrate! }.not_to raise_error
+ end
+ end
+
+ context 'with different migration statuses' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:status, :description) do
+ 0 | 'paused'
+ 1 | 'active'
+ 4 | 'failed'
+ 5 | 'finalizing'
+ end
+
+ with_them do
+ before do
+ destroy_invalid_member_migration.update!(status: status)
+ end
+
+ it_behaves_like 'finalizes the migration'
+ end
+ end
+ end
+ end
+end
diff --git a/spec/migrations/queue_backfill_user_details_fields_spec.rb b/spec/migrations/queue_backfill_user_details_fields_spec.rb
new file mode 100644
index 00000000000..388ac6d1bce
--- /dev/null
+++ b/spec/migrations/queue_backfill_user_details_fields_spec.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe QueueBackfillUserDetailsFields do
+ let_it_be(:batched_migration) { described_class::MIGRATION }
+
+ it 'schedules a new batched migration' do
+ reversible_migration do |migration|
+ migration.before -> {
+ expect(batched_migration).not_to have_scheduled_batched_migration
+ }
+
+ migration.after -> {
+ expect(batched_migration).to have_scheduled_batched_migration(
+ table_name: :users,
+ column_name: :id,
+ interval: described_class::INTERVAL
+ )
+ }
+ end
+ end
+end
diff --git a/spec/migrations/queue_populate_projects_star_count_spec.rb b/spec/migrations/queue_populate_projects_star_count_spec.rb
new file mode 100644
index 00000000000..848136d8005
--- /dev/null
+++ b/spec/migrations/queue_populate_projects_star_count_spec.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe QueuePopulateProjectsStarCount do
+ let_it_be(:batched_migration) { described_class::MIGRATION }
+
+ it 'schedules a new batched migration' do
+ reversible_migration do |migration|
+ migration.before -> {
+ expect(batched_migration).not_to have_scheduled_batched_migration
+ }
+
+ migration.after -> {
+ expect(batched_migration).to have_scheduled_batched_migration(
+ table_name: :projects,
+ column_name: :id,
+ interval: described_class::DELAY_INTERVAL
+ )
+ }
+ end
+ end
+end
diff --git a/spec/migrations/recount_epic_cache_counts_spec.rb b/spec/migrations/recount_epic_cache_counts_spec.rb
new file mode 100644
index 00000000000..56aa96cb40c
--- /dev/null
+++ b/spec/migrations/recount_epic_cache_counts_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe RecountEpicCacheCounts, :migration do
+ let(:migration) { described_class::MIGRATION }
+
+ describe '#up' do
+ it 'schedules a batched background migration' do
+ migrate!
+
+ expect(migration).to have_scheduled_batched_migration(
+ table_name: :epics,
+ column_name: :id,
+ interval: described_class::DELAY_INTERVAL,
+ batch_size: described_class::BATCH_SIZE,
+ max_batch_size: described_class::MAX_BATCH_SIZE,
+ sub_batch_size: described_class::SUB_BATCH_SIZE
+ )
+ end
+ end
+
+ describe '#down' do
+ it 'deletes all batched migration records' do
+ migrate!
+ schema_migrate_down!
+
+ expect(migration).not_to have_scheduled_batched_migration
+ end
+ end
+end
diff --git a/spec/migrations/reschedule_migrate_shared_vulnerability_scanners_spec.rb b/spec/migrations/reschedule_migrate_shared_vulnerability_scanners_spec.rb
new file mode 100644
index 00000000000..e8253f39c68
--- /dev/null
+++ b/spec/migrations/reschedule_migrate_shared_vulnerability_scanners_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+require_migration!
+
+RSpec.describe RescheduleMigrateSharedVulnerabilityScanners, :migration do
+ include Gitlab::Database::Migrations::BatchedBackgroundMigrationHelpers
+
+ def connection
+ ApplicationRecord.connection
+ end
+
+ describe "#up" do
+ before do
+ migrate!
+ end
+
+ it "schedules" do
+ expect(described_class::MIGRATION).to have_scheduled_batched_migration(
+ table_name: described_class::TABLE_NAME,
+ column_name: described_class::BATCH_COLUMN,
+ interval: described_class::DELAY_INTERVAL,
+ batch_size: described_class::BATCH_SIZE,
+ max_batch_size: described_class::BATCH_SIZE,
+ sub_batch_size: described_class::SUB_BATCH_SIZE,
+ gitlab_schema: :gitlab_main
+ )
+ end
+ end
+
+ describe '#down' do
+ before do
+ schema_migrate_down!
+ end
+
+ it "deletes" do
+ expect(described_class::MIGRATION).not_to have_scheduled_batched_migration
+ end
+ end
+end
diff --git a/spec/migrations/sanitize_confidential_note_todos_spec.rb b/spec/migrations/sanitize_confidential_note_todos_spec.rb
new file mode 100644
index 00000000000..00dece82cc1
--- /dev/null
+++ b/spec/migrations/sanitize_confidential_note_todos_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+require_migration!
+
+RSpec.describe SanitizeConfidentialNoteTodos do
+ let(:migration) { described_class::MIGRATION }
+
+ describe '#up' do
+ it 'schedules a batched background migration' do
+ migrate!
+
+ expect(migration).to have_scheduled_batched_migration(
+ table_name: :notes,
+ column_name: :id,
+ interval: described_class::DELAY_INTERVAL,
+ batch_size: described_class::BATCH_SIZE,
+ max_batch_size: described_class::MAX_BATCH_SIZE,
+ sub_batch_size: described_class::SUB_BATCH_SIZE
+ )
+ end
+ end
+
+ describe '#down' do
+ it 'deletes all batched migration records' do
+ migrate!
+ schema_migrate_down!
+
+ expect(migration).not_to have_scheduled_batched_migration
+ end
+ end
+end
diff --git a/spec/migrations/schedule_migrate_shared_vulnerability_scanners_spec.rb b/spec/migrations/schedule_migrate_shared_vulnerability_scanners_spec.rb
deleted file mode 100644
index f00d6568b67..00000000000
--- a/spec/migrations/schedule_migrate_shared_vulnerability_scanners_spec.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-# frozen_string_literal: true
-
-require "spec_helper"
-
-require_migration!
-
-RSpec.describe ScheduleMigrateSharedVulnerabilityScanners, :migration do
- describe "#up" do
- before do
- migrate!
- end
-
- it "schedules" do
- expect(described_class::MIGRATION).to have_scheduled_batched_migration(
- table_name: described_class::TABLE_NAME,
- column_name: described_class::BATCH_COLUMN,
- interval: described_class::DELAY_INTERVAL,
- batch_size: described_class::BATCH_SIZE,
- max_batch_size: described_class::BATCH_SIZE,
- sub_batch_size: described_class::SUB_BATCH_SIZE,
- gitlab_schema: :gitlab_main
- )
- end
-
- describe "ID range" do
- let(:expected_range) do
- { min_value: described_class::BATCH_MIN_VALUE,
- max_value: described_class::BATCH_MAX_VALUE }
- end
-
- subject do
- Gitlab::Database::BackgroundMigration::BatchedMigration
- .for_configuration(:gitlab_main,
- described_class::MIGRATION,
- described_class::TABLE_NAME,
- described_class::BATCH_COLUMN,
- [])
- end
-
- it "is set" do
- # The `have_scheduled_batched_migration` matcher accepts the
- # `batch_min_value` and `batch_max_value` keywords. However the respective
- # column names are `min_value` and `max_value`. Hence the matcher cannot
- # be used in this case, as it asserts the wrong attributes.
- expect(subject).to all(have_attributes(expected_range))
- end
- end
- end
-
- describe '#down' do
- before do
- schema_migrate_down!
- end
-
- it "deletes" do
- expect(described_class::MIGRATION).not_to have_scheduled_batched_migration
- end
- end
-end
diff --git a/spec/migrations/set_email_confirmation_setting_from_send_user_confirmation_email_setting_spec.rb b/spec/migrations/set_email_confirmation_setting_from_send_user_confirmation_email_setting_spec.rb
new file mode 100644
index 00000000000..761c0ef2fdb
--- /dev/null
+++ b/spec/migrations/set_email_confirmation_setting_from_send_user_confirmation_email_setting_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe SetEmailConfirmationSettingFromSendUserConfirmationEmailSetting do
+ let(:migration) { described_class.new }
+ let(:application_settings_table) { table(:application_settings) }
+
+ describe '#up' do
+ context "when 'send_user_confirmation_email' is set to 'true'" do
+ it "updates 'email_confirmation_setting' to '2' (hard)" do
+ application_settings_table.create!(send_user_confirmation_email: true, email_confirmation_setting: 0)
+
+ migration.up
+
+ expect(application_settings_table.last.email_confirmation_setting).to eq 2
+ end
+ end
+
+ context "when 'send_user_confirmation_email' is set to 'false'" do
+ it "updates 'email_confirmation_setting' to '0' (off)" do
+ application_settings_table.create!(send_user_confirmation_email: false, email_confirmation_setting: 0)
+
+ migration.up
+
+ expect(application_settings_table.last.email_confirmation_setting).to eq 0
+ end
+ end
+ end
+
+ describe '#down' do
+ it "updates 'email_confirmation_setting' to default value: '0' (off)" do
+ application_settings_table.create!(send_user_confirmation_email: true, email_confirmation_setting: 2)
+
+ migration.down
+
+ expect(application_settings_table.last.email_confirmation_setting).to eq 0
+ end
+ end
+end
diff --git a/spec/migrations/sync_new_amount_used_for_ci_namespace_monthly_usages_spec.rb b/spec/migrations/sync_new_amount_used_for_ci_namespace_monthly_usages_spec.rb
new file mode 100644
index 00000000000..9a17f375f82
--- /dev/null
+++ b/spec/migrations/sync_new_amount_used_for_ci_namespace_monthly_usages_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+require_migration!
+
+RSpec.describe SyncNewAmountUsedForCiNamespaceMonthlyUsages, migration: :gitlab_ci do
+ let(:namespace_usages) { table(:ci_namespace_monthly_usages) }
+
+ before do
+ # Disabling the trigger temporarily to allow records being created with out-of-sync
+ # `new_amount_used` and `amount_used`. This will simulate existing records before
+ # we add the trigger.
+ ActiveRecord::Base.connection
+ .execute("ALTER TABLE ci_namespace_monthly_usages DISABLE TRIGGER sync_namespaces_amount_used_columns")
+
+ this_month = Time.now.utc.beginning_of_month
+ last_month = 1.month.ago.utc.beginning_of_month
+ last_year = 1.year.ago.utc.beginning_of_month
+
+ namespace_usages.create!(namespace_id: 1, date: last_year)
+ namespace_usages.create!(namespace_id: 1, date: this_month, amount_used: 10, new_amount_used: 0)
+ namespace_usages.create!(namespace_id: 1, date: last_month, amount_used: 20, new_amount_used: 0)
+
+ namespace_usages.create!(namespace_id: 2, date: last_year)
+ namespace_usages.create!(namespace_id: 2, date: this_month, amount_used: 30, new_amount_used: 0)
+ namespace_usages.create!(namespace_id: 2, date: last_month, amount_used: 40, new_amount_used: 0)
+
+ ActiveRecord::Base.connection
+ .execute("ALTER TABLE ci_namespace_monthly_usages ENABLE TRIGGER sync_namespaces_amount_used_columns")
+ end
+
+ it 'updates `new_amount_used` with values from `amount_used`' do
+ expect(namespace_usages.where(new_amount_used: 0).count).to eq(6)
+
+ migrate!
+
+ expect(namespace_usages.where(new_amount_used: 0).count).to eq(2)
+ expect(namespace_usages.order(:id).pluck(:new_amount_used))
+ .to contain_exactly(0, 0, 10, 20, 30, 40)
+ end
+end
diff --git a/spec/migrations/sync_new_amount_used_for_ci_project_monthly_usages_spec.rb b/spec/migrations/sync_new_amount_used_for_ci_project_monthly_usages_spec.rb
new file mode 100644
index 00000000000..8d45f1107ea
--- /dev/null
+++ b/spec/migrations/sync_new_amount_used_for_ci_project_monthly_usages_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+require_migration!
+
+RSpec.describe SyncNewAmountUsedForCiProjectMonthlyUsages, migration: :gitlab_ci do
+ let(:project_usages) { table(:ci_project_monthly_usages) }
+
+ before do
+ # Disabling the trigger temporarily to allow records being created with out-of-sync
+ # `new_amount_used` and `amount_used`. This will simulate existing records before
+ # we add the trigger.
+ ActiveRecord::Base.connection
+ .execute("ALTER TABLE ci_project_monthly_usages DISABLE TRIGGER sync_projects_amount_used_columns")
+
+ this_month = Time.now.utc.beginning_of_month
+ last_month = 1.month.ago.utc.beginning_of_month
+ last_year = 1.year.ago.utc.beginning_of_month
+
+ project_usages.create!(project_id: 1, date: last_year)
+ project_usages.create!(project_id: 1, date: this_month, amount_used: 10, new_amount_used: 0)
+ project_usages.create!(project_id: 1, date: last_month, amount_used: 20, new_amount_used: 0)
+
+ project_usages.create!(project_id: 2, date: last_year)
+ project_usages.create!(project_id: 2, date: this_month, amount_used: 30, new_amount_used: 0)
+ project_usages.create!(project_id: 2, date: last_month, amount_used: 40, new_amount_used: 0)
+
+ ActiveRecord::Base.connection
+ .execute("ALTER TABLE ci_project_monthly_usages ENABLE TRIGGER sync_projects_amount_used_columns")
+ end
+
+ it 'updates `new_amount_used` with values from `amount_used`' do
+ expect(project_usages.where(new_amount_used: 0).count).to eq(6)
+
+ migrate!
+
+ expect(project_usages.where(new_amount_used: 0).count).to eq(2)
+ expect(project_usages.order(:id).pluck(:new_amount_used))
+ .to contain_exactly(0, 0, 10, 20, 30, 40)
+ end
+end