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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/migrations')
-rw-r--r--spec/migrations/20210430134202_copy_adoption_snapshot_namespace_spec.rb1
-rw-r--r--spec/migrations/20211214012507_backfill_incident_issue_escalation_statuses_spec.rb25
-rw-r--r--spec/migrations/20220204095121_backfill_namespace_statistics_with_dependency_proxy_size_spec.rb64
-rw-r--r--spec/migrations/20220223124428_schedule_merge_topics_with_same_name_spec.rb36
-rw-r--r--spec/migrations/20220315171129_cleanup_draft_data_from_faulty_regex_spec.rb40
-rw-r--r--spec/migrations/20220316202640_populate_container_repositories_migration_plan_spec.rb34
-rw-r--r--spec/migrations/20220321234317_remove_all_issuable_escalation_statuses_spec.rb20
-rw-r--r--spec/migrations/20220322132242_update_pages_onboarding_state_spec.rb53
-rw-r--r--spec/migrations/20220324032250_migrate_shimo_confluence_service_category_spec.rb34
-rw-r--r--spec/migrations/20220329175119_remove_leftover_ci_job_artifact_deletions_spec.rb92
-rw-r--r--spec/migrations/20220412143552_consume_remaining_encrypt_integration_property_jobs_spec.rb42
-rw-r--r--spec/migrations/add_epics_relative_position_spec.rb29
-rw-r--r--spec/migrations/backfill_group_features_spec.rb31
-rw-r--r--spec/migrations/backfill_namespace_id_for_project_routes_spec.rb29
-rw-r--r--spec/migrations/backfill_work_item_type_id_on_issues_spec.rb52
-rw-r--r--spec/migrations/cleanup_after_fixing_issue_when_admin_changed_primary_email_spec.rb40
-rw-r--r--spec/migrations/finalize_project_namespaces_backfill_spec.rb69
-rw-r--r--spec/migrations/finalize_traversal_ids_background_migrations_spec.rb60
-rw-r--r--spec/migrations/fix_and_backfill_project_namespaces_for_projects_with_duplicate_name_spec.rb51
-rw-r--r--spec/migrations/remove_wiki_notes_spec.rb33
-rw-r--r--spec/migrations/replace_work_item_type_backfill_next_batch_strategy_spec.rb55
21 files changed, 868 insertions, 22 deletions
diff --git a/spec/migrations/20210430134202_copy_adoption_snapshot_namespace_spec.rb b/spec/migrations/20210430134202_copy_adoption_snapshot_namespace_spec.rb
index 598da495195..ed18820ec8d 100644
--- a/spec/migrations/20210430134202_copy_adoption_snapshot_namespace_spec.rb
+++ b/spec/migrations/20210430134202_copy_adoption_snapshot_namespace_spec.rb
@@ -36,7 +36,6 @@ RSpec.describe CopyAdoptionSnapshotNamespace, :migration, schema: 20210430124630
runner_configured: true,
pipeline_succeeded: true,
deploy_succeeded: true,
- security_scan_succeeded: true,
end_time: Time.zone.now.end_of_month
}
diff --git a/spec/migrations/20211214012507_backfill_incident_issue_escalation_statuses_spec.rb b/spec/migrations/20211214012507_backfill_incident_issue_escalation_statuses_spec.rb
index a17fee6bab2..791c0595f0e 100644
--- a/spec/migrations/20211214012507_backfill_incident_issue_escalation_statuses_spec.rb
+++ b/spec/migrations/20211214012507_backfill_incident_issue_escalation_statuses_spec.rb
@@ -10,27 +10,10 @@ RSpec.describe BackfillIncidentIssueEscalationStatuses do
let(:namespace) { namespaces.create!(name: 'foo', path: 'foo') }
let(:project) { projects.create!(namespace_id: namespace.id) }
- before do
- stub_const("#{described_class.name}::BATCH_SIZE", 1)
- end
-
- it 'schedules jobs for incident issues' do
- issue_1 = issues.create!(project_id: project.id) # non-incident issue
- incident_1 = issues.create!(project_id: project.id, issue_type: 1)
- incident_2 = issues.create!(project_id: project.id, issue_type: 1)
-
- Sidekiq::Testing.fake! do
- freeze_time do
- migrate!
+ # Backfill removed - see db/migrate/20220321234317_remove_all_issuable_escalation_statuses.rb.
+ it 'does nothing' do
+ issues.create!(project_id: project.id, issue_type: 1)
- expect(described_class::MIGRATION).to be_scheduled_delayed_migration(
- 2.minutes, issue_1.id, issue_1.id)
- expect(described_class::MIGRATION).to be_scheduled_delayed_migration(
- 4.minutes, incident_1.id, incident_1.id)
- expect(described_class::MIGRATION).to be_scheduled_delayed_migration(
- 6.minutes, incident_2.id, incident_2.id)
- expect(BackgroundMigrationWorker.jobs.size).to eq(3)
- end
- end
+ expect { migrate! }.not_to change { BackgroundMigrationWorker.jobs.size }
end
end
diff --git a/spec/migrations/20220204095121_backfill_namespace_statistics_with_dependency_proxy_size_spec.rb b/spec/migrations/20220204095121_backfill_namespace_statistics_with_dependency_proxy_size_spec.rb
new file mode 100644
index 00000000000..39398fa058d
--- /dev/null
+++ b/spec/migrations/20220204095121_backfill_namespace_statistics_with_dependency_proxy_size_spec.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe BackfillNamespaceStatisticsWithDependencyProxySize do
+ let_it_be(:groups) { table(:namespaces) }
+ let_it_be(:group1) { groups.create!(id: 10, name: 'test1', path: 'test1', type: 'Group') }
+ let_it_be(:group2) { groups.create!(id: 20, name: 'test2', path: 'test2', type: 'Group') }
+ let_it_be(:group3) { groups.create!(id: 30, name: 'test3', path: 'test3', type: 'Group') }
+ let_it_be(:group4) { groups.create!(id: 40, name: 'test4', path: 'test4', type: 'Group') }
+
+ let_it_be(:dependency_proxy_blobs) { table(:dependency_proxy_blobs) }
+ let_it_be(:dependency_proxy_manifests) { table(:dependency_proxy_manifests) }
+
+ let_it_be(:group1_manifest) { create_manifest(10, 10) }
+ let_it_be(:group2_manifest) { create_manifest(20, 20) }
+ let_it_be(:group3_manifest) { create_manifest(30, 30) }
+
+ let_it_be(:group1_blob) { create_blob(10, 10) }
+ let_it_be(:group2_blob) { create_blob(20, 20) }
+ let_it_be(:group3_blob) { create_blob(30, 30) }
+
+ describe '#up' do
+ it 'correctly schedules background migrations' do
+ stub_const("#{described_class}::BATCH_SIZE", 2)
+
+ Sidekiq::Testing.fake! do
+ freeze_time do
+ migrate!
+
+ aggregate_failures do
+ expect(described_class::MIGRATION)
+ .to be_scheduled_migration([10, 30], ['dependency_proxy_size'])
+
+ expect(described_class::MIGRATION)
+ .to be_scheduled_delayed_migration(2.minutes, [20], ['dependency_proxy_size'])
+
+ expect(BackgroundMigrationWorker.jobs.size).to eq(2)
+ end
+ end
+ end
+ end
+ end
+
+ def create_manifest(group_id, size)
+ dependency_proxy_manifests.create!(
+ group_id: group_id,
+ size: size,
+ file_name: 'test-file',
+ file: 'test',
+ digest: 'abc123'
+ )
+ end
+
+ def create_blob(group_id, size)
+ dependency_proxy_blobs.create!(
+ group_id: group_id,
+ size: size,
+ file_name: 'test-file',
+ file: 'test'
+ )
+ end
+end
diff --git a/spec/migrations/20220223124428_schedule_merge_topics_with_same_name_spec.rb b/spec/migrations/20220223124428_schedule_merge_topics_with_same_name_spec.rb
new file mode 100644
index 00000000000..d9f6729475c
--- /dev/null
+++ b/spec/migrations/20220223124428_schedule_merge_topics_with_same_name_spec.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe ScheduleMergeTopicsWithSameName do
+ let(:topics) { table(:topics) }
+
+ describe '#up' do
+ before do
+ stub_const("#{described_class}::BATCH_SIZE", 2)
+
+ topics.create!(name: 'topic1')
+ topics.create!(name: 'Topic2')
+ topics.create!(name: 'Topic3')
+ topics.create!(name: 'Topic4')
+ topics.create!(name: 'topic2')
+ topics.create!(name: 'topic3')
+ topics.create!(name: 'topic4')
+ topics.create!(name: 'TOPIC2')
+ topics.create!(name: 'topic5')
+ end
+
+ it 'schedules MergeTopicsWithSameName background jobs', :aggregate_failures do
+ Sidekiq::Testing.fake! do
+ freeze_time do
+ migrate!
+
+ expect(described_class::MIGRATION).to be_scheduled_delayed_migration(2.minutes, %w[topic2 topic3])
+ expect(described_class::MIGRATION).to be_scheduled_delayed_migration(4.minutes, %w[topic4])
+ expect(BackgroundMigrationWorker.jobs.size).to eq(2)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/migrations/20220315171129_cleanup_draft_data_from_faulty_regex_spec.rb b/spec/migrations/20220315171129_cleanup_draft_data_from_faulty_regex_spec.rb
new file mode 100644
index 00000000000..925f1e573be
--- /dev/null
+++ b/spec/migrations/20220315171129_cleanup_draft_data_from_faulty_regex_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe CleanupDraftDataFromFaultyRegex do
+ let(:merge_requests) { table(:merge_requests) }
+
+ let!(:namespace) { table(:namespaces).create!(name: 'namespace', path: 'namespace') }
+ let!(:project) { table(:projects).create!(namespace_id: namespace.id) }
+
+ let(:default_mr_values) do
+ {
+ target_project_id: project.id,
+ draft: true,
+ source_branch: 'master',
+ target_branch: 'feature'
+ }
+ end
+
+ let!(:known_good_1) { merge_requests.create!(default_mr_values.merge(title: "Draft: Test Title")) }
+ let!(:known_good_2) { merge_requests.create!(default_mr_values.merge(title: "WIP: Test Title")) }
+ let!(:known_bad_1) { merge_requests.create!(default_mr_values.merge(title: "Known bad title drafts")) }
+ let!(:known_bad_2) { merge_requests.create!(default_mr_values.merge(title: "Known bad title wip")) }
+
+ describe '#up' do
+ it 'schedules CleanupDraftDataFromFaultyRegex background jobs filtering for eligble MRs' do
+ stub_const("#{described_class}::BATCH_SIZE", 2)
+ allow(Gitlab).to receive(:com?).and_return(true)
+
+ freeze_time do
+ migrate!
+
+ expect(described_class::MIGRATION).to be_scheduled_delayed_migration(5.minutes, known_bad_1.id, known_bad_2.id)
+
+ expect(BackgroundMigrationWorker.jobs.size).to eq(1)
+ end
+ end
+ end
+end
diff --git a/spec/migrations/20220316202640_populate_container_repositories_migration_plan_spec.rb b/spec/migrations/20220316202640_populate_container_repositories_migration_plan_spec.rb
new file mode 100644
index 00000000000..7b5c8254163
--- /dev/null
+++ b/spec/migrations/20220316202640_populate_container_repositories_migration_plan_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe PopulateContainerRepositoriesMigrationPlan, :aggregate_failures do
+ let_it_be(:namespaces) { table(:namespaces) }
+ let_it_be(:projects) { table(:projects) }
+ let_it_be(:container_repositories) { table(:container_repositories) }
+
+ let!(:namespace) { namespaces.create!(id: 1, name: 'namespace', path: 'namespace') }
+ let!(:project) { projects.create!(id: 1, name: 'project', path: 'project', namespace_id: 1) }
+ let!(:container_repository1) { container_repositories.create!(name: 'container_repository1', project_id: 1) }
+ let!(:container_repository2) { container_repositories.create!(name: 'container_repository2', project_id: 1) }
+ let!(:container_repository3) { container_repositories.create!(name: 'container_repository3', project_id: 1) }
+
+ before do
+ stub_const("#{described_class.name}::BATCH_SIZE", 2)
+ end
+
+ it 'schedules jobs for container_repositories to populate migration_state' do
+ Sidekiq::Testing.fake! do
+ freeze_time do
+ migrate!
+
+ expect(described_class::MIGRATION).to be_scheduled_delayed_migration(
+ 2.minutes, container_repository1.id, container_repository2.id)
+ expect(described_class::MIGRATION).to be_scheduled_delayed_migration(
+ 4.minutes, container_repository3.id, container_repository3.id)
+ expect(BackgroundMigrationWorker.jobs.size).to eq(2)
+ end
+ end
+ end
+end
diff --git a/spec/migrations/20220321234317_remove_all_issuable_escalation_statuses_spec.rb b/spec/migrations/20220321234317_remove_all_issuable_escalation_statuses_spec.rb
new file mode 100644
index 00000000000..44e20df1130
--- /dev/null
+++ b/spec/migrations/20220321234317_remove_all_issuable_escalation_statuses_spec.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe RemoveAllIssuableEscalationStatuses do
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:issues) { table(:issues) }
+ let(:statuses) { table(:incident_management_issuable_escalation_statuses) }
+ let(:namespace) { namespaces.create!(name: 'foo', path: 'foo') }
+ let(:project) { projects.create!(namespace_id: namespace.id) }
+
+ it 'removes all escalation status records' do
+ issue = issues.create!(project_id: project.id, issue_type: 1)
+ statuses.create!(issue_id: issue.id)
+
+ expect { migrate! }.to change(statuses, :count).from(1).to(0)
+ end
+end
diff --git a/spec/migrations/20220322132242_update_pages_onboarding_state_spec.rb b/spec/migrations/20220322132242_update_pages_onboarding_state_spec.rb
new file mode 100644
index 00000000000..fbd5fe546fa
--- /dev/null
+++ b/spec/migrations/20220322132242_update_pages_onboarding_state_spec.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+require 'spec_helper'
+require_migration!
+
+RSpec.describe UpdatePagesOnboardingState do
+ let(:migration) { described_class.new }
+ let!(:namespaces) { table(:namespaces) }
+ let!(:projects) { table(:projects) }
+ let!(:project_pages_metadata) { table(:project_pages_metadata) }
+
+ let!(:namespace1) { namespaces.create!(name: 'foo', path: 'foo') }
+ let!(:namespace2) { namespaces.create!(name: 'bar', path: 'bar') }
+ let!(:project1) { projects.create!(namespace_id: namespace1.id) }
+ let!(:project2) { projects.create!(namespace_id: namespace2.id) }
+ let!(:pages_metadata1) do
+ project_pages_metadata.create!(
+ project_id: project1.id,
+ deployed: true,
+ onboarding_complete: false
+ )
+ end
+
+ let!(:pages_metadata2) do
+ project_pages_metadata.create!(
+ project_id: project2.id,
+ deployed: false,
+ onboarding_complete: false
+ )
+ end
+
+ describe '#up' do
+ before do
+ migration.up
+ end
+
+ it 'sets the onboarding_complete attribute to the value of deployed' do
+ expect(pages_metadata1.reload.onboarding_complete).to eq(true)
+ expect(pages_metadata2.reload.onboarding_complete).to eq(false)
+ end
+ end
+
+ describe '#down' do
+ before do
+ migration.up
+ migration.down
+ end
+
+ it 'sets all onboarding_complete attributes to false' do
+ expect(pages_metadata1.reload.onboarding_complete).to eq(false)
+ expect(pages_metadata2.reload.onboarding_complete).to eq(false)
+ end
+ end
+end
diff --git a/spec/migrations/20220324032250_migrate_shimo_confluence_service_category_spec.rb b/spec/migrations/20220324032250_migrate_shimo_confluence_service_category_spec.rb
new file mode 100644
index 00000000000..38db6d51e7e
--- /dev/null
+++ b/spec/migrations/20220324032250_migrate_shimo_confluence_service_category_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe MigrateShimoConfluenceServiceCategory, :migration do
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:integrations) { table(:integrations) }
+
+ before do
+ namespace = namespaces.create!(name: 'test', path: 'test')
+ projects.create!(id: 1, namespace_id: namespace.id, name: 'gitlab', path: 'gitlab')
+ integrations.create!(id: 1, active: true, type_new: "Integrations::SlackSlashCommands",
+ category: 'chat', project_id: 1)
+ integrations.create!(id: 3, active: true, type_new: "Integrations::Confluence", category: 'common', project_id: 1)
+ integrations.create!(id: 5, active: true, type_new: "Integrations::Shimo", category: 'common', project_id: 1)
+ end
+
+ describe '#up' do
+ it 'correctly schedules background migrations', :aggregate_failures do
+ stub_const("#{described_class.name}::BATCH_SIZE", 2)
+
+ Sidekiq::Testing.fake! do
+ freeze_time do
+ migrate!
+
+ expect(described_class::MIGRATION).to be_scheduled_migration(3, 5)
+ expect(BackgroundMigrationWorker.jobs.size).to eq(1)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/migrations/20220329175119_remove_leftover_ci_job_artifact_deletions_spec.rb b/spec/migrations/20220329175119_remove_leftover_ci_job_artifact_deletions_spec.rb
new file mode 100644
index 00000000000..13884007af2
--- /dev/null
+++ b/spec/migrations/20220329175119_remove_leftover_ci_job_artifact_deletions_spec.rb
@@ -0,0 +1,92 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+require_migration!
+
+RSpec.describe RemoveLeftoverCiJobArtifactDeletions do
+ let(:deleted_records) { table(:loose_foreign_keys_deleted_records) }
+
+ target_table_name = Ci::JobArtifact.table_name
+
+ let(:pending_record1) do
+ deleted_records.create!(
+ id: 1,
+ fully_qualified_table_name: "public.#{target_table_name}",
+ primary_key_value: 1,
+ status: 1
+ )
+ end
+
+ let(:pending_record2) do
+ deleted_records.create!(
+ id: 2,
+ fully_qualified_table_name: "public.#{target_table_name}",
+ primary_key_value: 2,
+ status: 1
+ )
+ end
+
+ let(:other_pending_record1) do
+ deleted_records.create!(
+ id: 3,
+ fully_qualified_table_name: 'public.projects',
+ primary_key_value: 1,
+ status: 1
+ )
+ end
+
+ let(:other_pending_record2) do
+ deleted_records.create!(
+ id: 4,
+ fully_qualified_table_name: 'public.ci_builds',
+ primary_key_value: 1,
+ status: 1
+ )
+ end
+
+ let(:processed_record1) do
+ deleted_records.create!(
+ id: 5,
+ fully_qualified_table_name: 'public.external_pull_requests',
+ primary_key_value: 3,
+ status: 2
+ )
+ end
+
+ let(:other_processed_record1) do
+ deleted_records.create!(
+ id: 6,
+ fully_qualified_table_name: 'public.ci_builds',
+ primary_key_value: 2,
+ status: 2
+ )
+ end
+
+ let!(:persisted_ids_before) do
+ [
+ pending_record1,
+ pending_record2,
+ other_pending_record1,
+ other_pending_record2,
+ processed_record1,
+ other_processed_record1
+ ].map(&:id).sort
+ end
+
+ let!(:persisted_ids_after) do
+ [
+ other_pending_record1,
+ other_pending_record2,
+ processed_record1,
+ other_processed_record1
+ ].map(&:id).sort
+ end
+
+ def all_ids
+ deleted_records.all.map(&:id).sort
+ end
+
+ it 'deletes pending external_pull_requests records' do
+ expect { migrate! }.to change { all_ids }.from(persisted_ids_before).to(persisted_ids_after)
+ end
+end
diff --git a/spec/migrations/20220412143552_consume_remaining_encrypt_integration_property_jobs_spec.rb b/spec/migrations/20220412143552_consume_remaining_encrypt_integration_property_jobs_spec.rb
new file mode 100644
index 00000000000..4a1b68a5a85
--- /dev/null
+++ b/spec/migrations/20220412143552_consume_remaining_encrypt_integration_property_jobs_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+require_migration!
+
+RSpec.describe ConsumeRemainingEncryptIntegrationPropertyJobs, :migration do
+ subject(:migration) { described_class.new }
+
+ let(:integrations) { table(:integrations) }
+ let(:bg_migration_class) { ::Gitlab::BackgroundMigration::EncryptIntegrationProperties }
+ let(:bg_migration) { instance_double(bg_migration_class) }
+
+ before do
+ stub_const("#{described_class.name}::BATCH_SIZE", 2)
+ end
+
+ it 'performs remaining background migrations', :aggregate_failures do
+ # Already migrated
+ integrations.create!(properties: some_props, encrypted_properties: 'abc')
+ integrations.create!(properties: some_props, encrypted_properties: 'def')
+ integrations.create!(properties: some_props, encrypted_properties: 'xyz')
+ # update required
+ record1 = integrations.create!(properties: some_props)
+ record2 = integrations.create!(properties: some_props)
+ record3 = integrations.create!(properties: some_props)
+ # No update required
+ integrations.create!(properties: nil)
+ integrations.create!(properties: nil)
+
+ expect(Gitlab::BackgroundMigration).to receive(:steal).with(bg_migration_class.name.demodulize)
+ expect(bg_migration_class).to receive(:new).twice.and_return(bg_migration)
+ expect(bg_migration).to receive(:perform).with(record1.id, record2.id)
+ expect(bg_migration).to receive(:perform).with(record3.id, record3.id)
+
+ migrate!
+ end
+
+ def some_props
+ { iid: generate(:iid), url: generate(:url), username: generate(:username) }.to_json
+ end
+end
diff --git a/spec/migrations/add_epics_relative_position_spec.rb b/spec/migrations/add_epics_relative_position_spec.rb
new file mode 100644
index 00000000000..f3b7dd1727b
--- /dev/null
+++ b/spec/migrations/add_epics_relative_position_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+require_migration!
+
+RSpec.describe AddEpicsRelativePosition, :migration do
+ let(:groups) { table(:namespaces) }
+ let(:epics) { table(:epics) }
+ let(:users) { table(:users) }
+ let(:user) { users.create!(name: 'user', email: 'email@example.org', projects_limit: 100) }
+ let(:group) { groups.create!(name: 'gitlab', path: 'gitlab-org', type: 'Group') }
+
+ let!(:epic1) { epics.create!(title: 'epic 1', title_html: 'epic 1', author_id: user.id, group_id: group.id, iid: 1) }
+ let!(:epic2) { epics.create!(title: 'epic 2', title_html: 'epic 2', author_id: user.id, group_id: group.id, iid: 2) }
+ let!(:epic3) { epics.create!(title: 'epic 3', title_html: 'epic 3', author_id: user.id, group_id: group.id, iid: 3) }
+
+ it 'does nothing if epics table contains relative_position' do
+ expect { migrate! }.not_to change { epics.pluck(:relative_position) }
+ end
+
+ it 'adds relative_position if missing and backfills it with ID value', :aggregate_failures do
+ ActiveRecord::Base.connection.execute('ALTER TABLE epics DROP relative_position')
+
+ migrate!
+
+ expect(epics.pluck(:relative_position)).to match_array([epic1.id * 500, epic2.id * 500, epic3.id * 500])
+ end
+end
diff --git a/spec/migrations/backfill_group_features_spec.rb b/spec/migrations/backfill_group_features_spec.rb
new file mode 100644
index 00000000000..922d54f43be
--- /dev/null
+++ b/spec/migrations/backfill_group_features_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe BackfillGroupFeatures, :migration do
+ let(:migration) { described_class::MIGRATION }
+
+ describe '#up' do
+ it 'schedules background jobs for each batch of namespaces' do
+ migrate!
+
+ expect(migration).to have_scheduled_batched_migration(
+ table_name: :namespaces,
+ column_name: :id,
+ job_arguments: [described_class::BATCH_SIZE],
+ interval: described_class::INTERVAL,
+ batch_size: described_class::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/backfill_namespace_id_for_project_routes_spec.rb b/spec/migrations/backfill_namespace_id_for_project_routes_spec.rb
new file mode 100644
index 00000000000..28edd17731f
--- /dev/null
+++ b/spec/migrations/backfill_namespace_id_for_project_routes_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe BackfillNamespaceIdForProjectRoutes, :migration do
+ let(:migration) { described_class::MIGRATION }
+
+ describe '#up' do
+ it 'schedules background jobs for each batch of group members' do
+ migrate!
+
+ expect(migration).to have_scheduled_batched_migration(
+ table_name: :routes,
+ column_name: :id,
+ interval: described_class::INTERVAL
+ )
+ 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/backfill_work_item_type_id_on_issues_spec.rb b/spec/migrations/backfill_work_item_type_id_on_issues_spec.rb
new file mode 100644
index 00000000000..6798b0cc7e8
--- /dev/null
+++ b/spec/migrations/backfill_work_item_type_id_on_issues_spec.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe BackfillWorkItemTypeIdOnIssues, :migration do
+ let_it_be(:migration) { described_class::MIGRATION }
+ let_it_be(:interval) { 2.minutes }
+ let_it_be(:issue_type_enum) { { issue: 0, incident: 1, test_case: 2, requirement: 3, task: 4 } }
+ let_it_be(:base_work_item_type_ids) do
+ table(:work_item_types).where(namespace_id: nil).order(:base_type).each_with_object({}) do |type, hash|
+ hash[type.base_type] = type.id
+ end
+ end
+
+ describe '#up' do
+ it 'correctly schedules background migrations' do
+ Sidekiq::Testing.fake! do
+ freeze_time do
+ migrate!
+
+ scheduled_migrations = Gitlab::Database::BackgroundMigration::BatchedMigration.where(job_class_name: migration)
+ work_item_types = table(:work_item_types).where(namespace_id: nil)
+
+ expect(scheduled_migrations.count).to eq(work_item_types.count)
+
+ [:issue, :incident, :test_case, :requirement, :task].each do |issue_type|
+ expect(migration).to have_scheduled_batched_migration(
+ table_name: :issues,
+ column_name: :id,
+ job_arguments: [issue_type_enum[issue_type], base_work_item_type_ids[issue_type_enum[issue_type]]],
+ interval: interval,
+ batch_size: described_class::BATCH_SIZE,
+ max_batch_size: described_class::MAX_BATCH_SIZE,
+ sub_batch_size: described_class::SUB_BATCH_SIZE,
+ batch_class_name: described_class::BATCH_CLASS_NAME
+ )
+ end
+ end
+ end
+ 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/cleanup_after_fixing_issue_when_admin_changed_primary_email_spec.rb b/spec/migrations/cleanup_after_fixing_issue_when_admin_changed_primary_email_spec.rb
new file mode 100644
index 00000000000..eda57545c7a
--- /dev/null
+++ b/spec/migrations/cleanup_after_fixing_issue_when_admin_changed_primary_email_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe CleanupAfterFixingIssueWhenAdminChangedPrimaryEmail, :sidekiq do
+ let(:migration) { described_class.new }
+ let(:users) { table(:users) }
+ let(:emails) { table(:emails) }
+
+ let!(:user_1) { users.create!(name: 'confirmed-user-1', email: 'confirmed-1@example.com', confirmed_at: 3.days.ago, projects_limit: 100) }
+ let!(:user_2) { users.create!(name: 'confirmed-user-2', email: 'confirmed-2@example.com', confirmed_at: 1.day.ago, projects_limit: 100) }
+ let!(:user_3) { users.create!(name: 'confirmed-user-3', email: 'confirmed-3@example.com', confirmed_at: 1.day.ago, projects_limit: 100) }
+ let!(:user_4) { users.create!(name: 'unconfirmed-user', email: 'unconfirmed@example.com', confirmed_at: nil, projects_limit: 100) }
+
+ let!(:email_1) { emails.create!(email: 'confirmed-1@example.com', user_id: user_1.id, confirmed_at: 1.day.ago) }
+ let!(:email_2) { emails.create!(email: 'other_2@example.com', user_id: user_2.id, confirmed_at: 1.day.ago) }
+
+ before do
+ stub_const("#{described_class.name}::BATCH_SIZE", 2)
+ end
+
+ it 'adds the primary email to emails for leftover confirmed users that do not have their primary email in the emails table', :aggregate_failures do
+ original_email_1_confirmed_at = email_1.reload.confirmed_at
+
+ expect { migration.up }.to change { emails.count }.by(2)
+
+ expect(emails.find_by(user_id: user_2.id, email: 'confirmed-2@example.com').confirmed_at).to eq(user_2.reload.confirmed_at)
+ expect(emails.find_by(user_id: user_3.id, email: 'confirmed-3@example.com').confirmed_at).to eq(user_3.reload.confirmed_at)
+ expect(email_1.reload.confirmed_at).to eq(original_email_1_confirmed_at)
+
+ expect(emails.exists?(user_id: user_4.id)).to be(false)
+ end
+
+ it 'continues in case of errors with one email' do
+ allow(Email).to receive(:create) { raise 'boom!' }
+
+ expect { migration.up }.not_to raise_error
+ end
+end
diff --git a/spec/migrations/finalize_project_namespaces_backfill_spec.rb b/spec/migrations/finalize_project_namespaces_backfill_spec.rb
new file mode 100644
index 00000000000..3d0b0ec13fe
--- /dev/null
+++ b/spec/migrations/finalize_project_namespaces_backfill_spec.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe FinalizeProjectNamespacesBackfill, :migration do
+ let(:batched_migrations) { table(:batched_background_migrations) }
+
+ let_it_be(:migration) { described_class::MIGRATION }
+
+ describe '#up' do
+ shared_examples 'raises migration not finished exception' do
+ it 'raises exception' do
+ expect { migrate! }.to raise_error(/Expected batched background migration for the given configuration to be marked as 'finished'/)
+ end
+ end
+
+ context 'when project namespace backfilling 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 backfilling migration present' do
+ let!(:project_namespace_backfill) do
+ batched_migrations.create!(
+ job_class_name: 'ProjectNamespaces::BackfillProjectNamespaces',
+ table_name: :projects,
+ column_name: :id,
+ job_arguments: [nil, 'up'],
+ interval: 2.minutes,
+ min_value: 1,
+ max_value: 2,
+ batch_size: 1000,
+ sub_batch_size: 200,
+ status: 3 # finished
+ )
+ end
+
+ context 'when project namespace backfilling migration finished successfully' do
+ it 'does not raise exception' do
+ expect { migrate! }.not_to raise_error(/Expected batched background migration for the given configuration to be marked as 'finished'/)
+ end
+ end
+
+ context 'when project namespace backfilling migration is paused' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:status, :description) do
+ 0 | 'paused'
+ 1 | 'active'
+ 4 | 'failed'
+ 5 | 'finalizing'
+ end
+
+ with_them do
+ before do
+ project_namespace_backfill.update!(status: status)
+ end
+
+ it_behaves_like 'raises migration not finished exception'
+ end
+ end
+ end
+ end
+end
diff --git a/spec/migrations/finalize_traversal_ids_background_migrations_spec.rb b/spec/migrations/finalize_traversal_ids_background_migrations_spec.rb
new file mode 100644
index 00000000000..74d6447e6a7
--- /dev/null
+++ b/spec/migrations/finalize_traversal_ids_background_migrations_spec.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!('finalize_traversal_ids_background_migrations')
+
+RSpec.describe FinalizeTraversalIdsBackgroundMigrations, :migration do
+ shared_context 'incomplete background migration' do
+ before do
+ # Jobs enqueued in Sidekiq.
+ Sidekiq::Testing.disable! do
+ BackgroundMigrationWorker.perform_in(10, job_class_name, [1, 2, 100])
+ BackgroundMigrationWorker.perform_in(20, job_class_name, [3, 4, 100])
+ end
+
+ # Jobs tracked in the database.
+ # table(:background_migration_jobs).create!(
+ Gitlab::Database::BackgroundMigrationJob.create!(
+ class_name: job_class_name,
+ arguments: [5, 6, 100],
+ status: Gitlab::Database::BackgroundMigrationJob.statuses['pending']
+ )
+ # table(:background_migration_jobs).create!(
+ Gitlab::Database::BackgroundMigrationJob.create!(
+ class_name: job_class_name,
+ arguments: [7, 8, 100],
+ status: Gitlab::Database::BackgroundMigrationJob.statuses['succeeded']
+ )
+ end
+ end
+
+ context 'BackfillNamespaceTraversalIdsRoots background migration' do
+ let(:job_class_name) { 'BackfillNamespaceTraversalIdsRoots' }
+
+ include_context 'incomplete background migration'
+
+ before do
+ migrate!
+ end
+
+ it_behaves_like(
+ 'finalized tracked background migration',
+ Gitlab::BackgroundMigration::BackfillNamespaceTraversalIdsRoots
+ )
+ end
+
+ context 'BackfillNamespaceTraversalIdsChildren background migration' do
+ let(:job_class_name) { 'BackfillNamespaceTraversalIdsChildren' }
+
+ include_context 'incomplete background migration'
+
+ before do
+ migrate!
+ end
+
+ it_behaves_like(
+ 'finalized tracked background migration',
+ Gitlab::BackgroundMigration::BackfillNamespaceTraversalIdsChildren
+ )
+ end
+end
diff --git a/spec/migrations/fix_and_backfill_project_namespaces_for_projects_with_duplicate_name_spec.rb b/spec/migrations/fix_and_backfill_project_namespaces_for_projects_with_duplicate_name_spec.rb
new file mode 100644
index 00000000000..44a2220b2ad
--- /dev/null
+++ b/spec/migrations/fix_and_backfill_project_namespaces_for_projects_with_duplicate_name_spec.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe FixAndBackfillProjectNamespacesForProjectsWithDuplicateName, :migration do
+ let(:projects) { table(:projects) }
+ let(:namespaces) { table(:namespaces) }
+
+ let!(:group) { namespaces.create!(name: 'group1', path: 'group1', type: 'Group') }
+ let!(:project_namespace) { namespaces.create!(name: 'project2', path: 'project2', type: 'Project') }
+ let!(:project1) { projects.create!(name: 'project1', path: 'project1', project_namespace_id: nil, namespace_id: group.id, visibility_level: 20) }
+ let!(:project2) { projects.create!(name: 'project2', path: 'project2', project_namespace_id: project_namespace.id, namespace_id: group.id, visibility_level: 20) }
+ let!(:project3) { projects.create!(name: 'project3', path: 'project3', project_namespace_id: nil, namespace_id: group.id, visibility_level: 20) }
+ let!(:project4) { projects.create!(name: 'project4', path: 'project4', project_namespace_id: nil, namespace_id: group.id, visibility_level: 20) }
+
+ describe '#up' do
+ it 'schedules background migrations' do
+ Sidekiq::Testing.fake! do
+ freeze_time do
+ described_class.new.up
+
+ migration = described_class::MIGRATION
+
+ expect(migration).to be_scheduled_delayed_migration(2.minutes, project1.id, project4.id)
+ expect(BackgroundMigrationWorker.jobs.size).to eq 1
+ end
+ end
+ end
+
+ context 'in batches' do
+ before do
+ stub_const('FixAndBackfillProjectNamespacesForProjectsWithDuplicateName::BATCH_SIZE', 2)
+ end
+
+ it 'schedules background migrations' do
+ Sidekiq::Testing.fake! do
+ freeze_time do
+ described_class.new.up
+
+ migration = described_class::MIGRATION
+
+ expect(migration).to be_scheduled_delayed_migration(2.minutes, project1.id, project3.id)
+ expect(migration).to be_scheduled_delayed_migration(4.minutes, project4.id, project4.id)
+ expect(BackgroundMigrationWorker.jobs.size).to eq 2
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/migrations/remove_wiki_notes_spec.rb b/spec/migrations/remove_wiki_notes_spec.rb
new file mode 100644
index 00000000000..2ffebdee106
--- /dev/null
+++ b/spec/migrations/remove_wiki_notes_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe RemoveWikiNotes, :migration do
+ let(:notes) { table(:notes) }
+
+ it 'removes all wiki notes' do
+ notes.create!(id: 97, note: 'Wiki note', noteable_type: 'Wiki')
+ notes.create!(id: 98, note: 'Commit note', noteable_type: 'Commit')
+ notes.create!(id: 110, note: 'Issue note', noteable_type: 'Issue')
+ notes.create!(id: 242, note: 'MergeRequest note', noteable_type: 'MergeRequest')
+
+ expect(notes.where(noteable_type: 'Wiki').size).to eq(1)
+
+ expect { migrate! }.to change { notes.count }.by(-1)
+
+ expect(notes.where(noteable_type: 'Wiki').size).to eq(0)
+ end
+
+ context 'when not staging nor com' do
+ it 'does not remove notes' do
+ allow(::Gitlab).to receive(:com?).and_return(false)
+ allow(::Gitlab).to receive(:dev_or_test_env?).and_return(false)
+ allow(::Gitlab).to receive(:staging?).and_return(false)
+
+ notes.create!(id: 97, note: 'Wiki note', noteable_type: 'Wiki')
+
+ expect { migrate! }.not_to change { notes.count }
+ end
+ end
+end
diff --git a/spec/migrations/replace_work_item_type_backfill_next_batch_strategy_spec.rb b/spec/migrations/replace_work_item_type_backfill_next_batch_strategy_spec.rb
new file mode 100644
index 00000000000..5e22fc06973
--- /dev/null
+++ b/spec/migrations/replace_work_item_type_backfill_next_batch_strategy_spec.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe ReplaceWorkItemTypeBackfillNextBatchStrategy, :migration do
+ describe '#up' do
+ it 'sets the new strategy for existing migrations' do
+ migrations = create_migrations(described_class::OLD_STRATEGY_CLASS, 2)
+
+ expect do
+ migrate!
+
+ migrations.each(&:reload)
+ end.to change { migrations.pluck(:batch_class_name).uniq }.from([described_class::OLD_STRATEGY_CLASS])
+ .to([described_class::NEW_STRATEGY_CLASS])
+ end
+ end
+
+ describe '#down' do
+ it 'sets the old strategy for existing migrations' do
+ migrations = create_migrations(described_class::NEW_STRATEGY_CLASS, 2)
+
+ expect do
+ migrate!
+ schema_migrate_down!
+
+ migrations.each(&:reload)
+ end.to change { migrations.pluck(:batch_class_name).uniq }.from([described_class::NEW_STRATEGY_CLASS])
+ .to([described_class::OLD_STRATEGY_CLASS])
+ end
+ end
+
+ def create_migrations(batch_class_name, count)
+ Array.new(2) { |index| create_background_migration(batch_class_name, [index]) }
+ end
+
+ def create_background_migration(batch_class_name, job_arguments)
+ migrations_table = table(:batched_background_migrations)
+
+ migrations_table.create!(
+ batch_class_name: batch_class_name,
+ job_class_name: described_class::JOB_CLASS_NAME,
+ max_value: 10,
+ batch_size: 5,
+ sub_batch_size: 1,
+ interval: 2.minutes,
+ table_name: :issues,
+ column_name: :id,
+ total_tuple_count: 10_000,
+ pause_ms: 100,
+ job_arguments: job_arguments
+ )
+ end
+end