diff options
Diffstat (limited to 'spec/migrations')
10 files changed, 463 insertions, 1 deletions
diff --git a/spec/migrations/20200929052138_create_initial_versions_for_pre_versioning_terraform_states_spec.rb b/spec/migrations/20200929052138_create_initial_versions_for_pre_versioning_terraform_states_spec.rb new file mode 100644 index 00000000000..1a618712b32 --- /dev/null +++ b/spec/migrations/20200929052138_create_initial_versions_for_pre_versioning_terraform_states_spec.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20200929052138_create_initial_versions_for_pre_versioning_terraform_states.rb') + +RSpec.describe CreateInitialVersionsForPreVersioningTerraformStates do + let(:namespace) { table(:namespaces).create!(name: 'terraform', path: 'terraform') } + let(:project) { table(:projects).create!(id: 1, namespace_id: namespace.id) } + let(:terraform_state_versions) { table(:terraform_state_versions) } + + def create_state!(project, versioning_enabled:) + table(:terraform_states).create!( + project_id: project.id, + uuid: 'uuid', + file_store: 2, + file: 'state.tfstate', + versioning_enabled: versioning_enabled + ) + end + + describe '#up' do + context 'for a state that is already versioned' do + let!(:terraform_state) { create_state!(project, versioning_enabled: true) } + + it 'does not insert a version record' do + expect { migrate! }.not_to change { terraform_state_versions.count } + end + end + + context 'for a state that is not yet versioned' do + let!(:terraform_state) { create_state!(project, versioning_enabled: false) } + + it 'creates a version using the current state data' do + expect { migrate! }.to change { terraform_state_versions.count }.by(1) + + migrated_version = terraform_state_versions.last + expect(migrated_version.terraform_state_id).to eq(terraform_state.id) + expect(migrated_version.version).to be_zero + expect(migrated_version.file_store).to eq(terraform_state.file_store) + expect(migrated_version.file).to eq(terraform_state.file) + expect(migrated_version.created_at).to be_present + expect(migrated_version.updated_at).to be_present + end + end + end +end diff --git a/spec/migrations/20201014205300_drop_backfill_jira_tracker_deployment_type_jobs_spec.rb b/spec/migrations/20201014205300_drop_backfill_jira_tracker_deployment_type_jobs_spec.rb new file mode 100644 index 00000000000..134bea6b666 --- /dev/null +++ b/spec/migrations/20201014205300_drop_backfill_jira_tracker_deployment_type_jobs_spec.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'migrate', '20201014205300_drop_backfill_jira_tracker_deployment_type_jobs.rb') + +RSpec.describe DropBackfillJiraTrackerDeploymentTypeJobs, :sidekiq, :redis, schema: 2020_10_14_205300 do + subject(:migration) { described_class.new } + + describe '#up' do + let(:retry_set) { Sidekiq::RetrySet.new } + let(:scheduled_set) { Sidekiq::ScheduledSet.new } + + context 'there are only affected jobs on the queue' do + let(:payload) { { 'class' => ::BackgroundMigrationWorker, 'args' => [described_class::DROPPED_JOB_CLASS, 1] } } + let(:queue_payload) { payload.merge('queue' => described_class::QUEUE) } + + it 'removes enqueued BackfillJiraTrackerDeploymentType background jobs' do + Sidekiq::Testing.disable! do # https://github.com/mperham/sidekiq/wiki/testing#api Sidekiq's API does not have a testing mode + retry_set.schedule(1.hour.from_now, payload) + scheduled_set.schedule(1.hour.from_now, payload) + Sidekiq::Client.push(queue_payload) + + expect { migration.up }.to change { Sidekiq::Queue.new(described_class::QUEUE).size }.from(1).to(0) + expect(retry_set.size).to eq(0) + expect(scheduled_set.size).to eq(0) + end + end + end + + context 'there are not any affected jobs on the queue' do + let(:payload) { { 'class' => ::BackgroundMigrationWorker, 'args' => ['SomeOtherClass', 1] } } + let(:queue_payload) { payload.merge('queue' => described_class::QUEUE) } + + it 'skips other enqueued jobs' do + Sidekiq::Testing.disable! do + retry_set.schedule(1.hour.from_now, payload) + scheduled_set.schedule(1.hour.from_now, payload) + Sidekiq::Client.push(queue_payload) + + expect { migration.up }.not_to change { Sidekiq::Queue.new(described_class::QUEUE).size } + expect(retry_set.size).to eq(1) + expect(scheduled_set.size).to eq(1) + end + end + end + + context 'other queues' do + it 'does not modify them' do + Sidekiq::Testing.disable! do + Sidekiq::Client.push('queue' => 'other', 'class' => ::BackgroundMigrationWorker, 'args' => ['SomeOtherClass', 1]) + Sidekiq::Client.push('queue' => 'other', 'class' => ::BackgroundMigrationWorker, 'args' => [described_class::DROPPED_JOB_CLASS, 1]) + + expect { migration.up }.not_to change { Sidekiq::Queue.new('other').size } + end + end + end + end +end diff --git a/spec/migrations/add_partial_index_to_ci_builds_table_on_user_id_name_spec.rb b/spec/migrations/add_partial_index_to_ci_builds_table_on_user_id_name_spec.rb new file mode 100644 index 00000000000..018d48bea66 --- /dev/null +++ b/spec/migrations/add_partial_index_to_ci_builds_table_on_user_id_name_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20200908064229_add_partial_index_to_ci_builds_table_on_user_id_name.rb') + +RSpec.describe AddPartialIndexToCiBuildsTableOnUserIdName do + let(:migration) { described_class.new } + + describe '#up' do + it 'creates temporary partial index on type' do + expect { migration.up }.to change { migration.index_exists?(:ci_builds, [:user_id, :name], name: described_class::INDEX_NAME) }.from(false).to(true) + end + end + + describe '#down' do + it 'removes temporary partial index on type' do + migration.up + + expect { migration.down }.to change { migration.index_exists?(:ci_builds, [:user_id, :name], name: described_class::INDEX_NAME) }.from(true).to(false) + end + end +end diff --git a/spec/migrations/backfill_status_page_published_incidents_spec.rb b/spec/migrations/backfill_status_page_published_incidents_spec.rb index 2b1ab891038..674484cdf0a 100644 --- a/spec/migrations/backfill_status_page_published_incidents_spec.rb +++ b/spec/migrations/backfill_status_page_published_incidents_spec.rb @@ -37,7 +37,7 @@ RSpec.describe BackfillStatusPagePublishedIncidents, :migration do end it 'creates a StatusPage::PublishedIncident record for each published issue' do - Timecop.freeze(current_time) do + travel_to(current_time) do expect(incidents.all).to be_empty migrate! diff --git a/spec/migrations/cleanup_group_import_states_with_null_user_id_spec.rb b/spec/migrations/cleanup_group_import_states_with_null_user_id_spec.rb new file mode 100644 index 00000000000..f9285c857de --- /dev/null +++ b/spec/migrations/cleanup_group_import_states_with_null_user_id_spec.rb @@ -0,0 +1,101 @@ +# frozen_string_literal: true + +# In order to test the CleanupGroupImportStatesWithNullUserId migration, we need +# to first create GroupImportState with NULL user_id +# and then run the migration to check that user_id was populated or record removed +# +# The problem is that the CleanupGroupImportStatesWithNullUserId migration comes +# after the NOT NULL constraint has been added with a previous migration (AddNotNullConstraintToUserOnGroupImportStates) +# That means that while testing the current class we can not insert GroupImportState records with an +# invalid user_id as constraint is blocking it from doing so +# +# To solve this problem, use SchemaVersionFinder to set schema one version prior to AddNotNullConstraintToUserOnGroupImportStates + +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20200907092715_add_not_null_constraint_to_user_on_group_import_states.rb') +require Rails.root.join('db', 'post_migrate', '20200909161624_cleanup_group_import_states_with_null_user_id.rb') + +RSpec.describe CleanupGroupImportStatesWithNullUserId, :migration, + schema: MigrationHelpers::SchemaVersionFinder.migration_prior(AddNotNullConstraintToUserOnGroupImportStates) do + let(:namespaces_table) { table(:namespaces) } + let(:users_table) { table(:users) } + let(:group_import_states_table) { table(:group_import_states) } + let(:members_table) { table(:members) } + + describe 'Group import states clean up' do + context 'when user_id is present' do + it 'does not update group_import_state record' do + user_1 = users_table.create!(name: 'user1', email: 'user1@example.com', projects_limit: 1) + group_1 = namespaces_table.create!(name: 'group_1', path: 'group_1', type: 'Group') + create_member(user_id: user_1.id, type: 'GroupMember', source_type: 'Namespace', source_id: group_1.id, access_level: described_class::Group::OWNER) + group_import_state_1 = group_import_states_table.create!(group_id: group_1.id, user_id: user_1.id, status: 0) + + expect(group_import_state_1.user_id).to eq(user_1.id) + + disable_migrations_output { migrate! } + + expect(group_import_state_1.reload.user_id).to eq(user_1.id) + end + end + + context 'when user_id is missing' do + it 'updates user_id with group default owner id' do + user_2 = users_table.create!(name: 'user2', email: 'user2@example.com', projects_limit: 1) + group_2 = namespaces_table.create!(name: 'group_2', path: 'group_2', type: 'Group') + create_member(user_id: user_2.id, type: 'GroupMember', source_type: 'Namespace', source_id: group_2.id, access_level: described_class::Group::OWNER) + group_import_state_2 = group_import_states_table.create!(group_id: group_2.id, user_id: nil, status: 0) + + disable_migrations_output { migrate! } + + expect(group_import_state_2.reload.user_id).to eq(user_2.id) + end + end + + context 'when group does not contain any owners' do + it 'removes group_import_state record' do + group_3 = namespaces_table.create!(name: 'group_3', path: 'group_3', type: 'Group') + group_import_state_3 = group_import_states_table.create!(group_id: group_3.id, user_id: nil, status: 0) + + disable_migrations_output { migrate! } + + expect { group_import_state_3.reload }.to raise_error(ActiveRecord::RecordNotFound) + end + end + + context 'when group has parent' do + it 'updates user_id with parent group default owner id' do + user = users_table.create!(name: 'user4', email: 'user4@example.com', projects_limit: 1) + group_1 = namespaces_table.create!(name: 'group_1', path: 'group_1', type: 'Group') + create_member(user_id: user.id, type: 'GroupMember', source_type: 'Namespace', source_id: group_1.id, access_level: described_class::Group::OWNER) + group_2 = namespaces_table.create!(name: 'group_2', path: 'group_2', type: 'Group', parent_id: group_1.id) + group_import_state = group_import_states_table.create!(group_id: group_2.id, user_id: nil, status: 0) + + disable_migrations_output { migrate! } + + expect(group_import_state.reload.user_id).to eq(user.id) + end + end + + context 'when group has owner_id' do + it 'updates user_id with owner_id' do + user = users_table.create!(name: 'user', email: 'user@example.com', projects_limit: 1) + group = namespaces_table.create!(name: 'group', path: 'group', type: 'Group', owner_id: user.id) + group_import_state = group_import_states_table.create!(group_id: group.id, user_id: nil, status: 0) + + disable_migrations_output { migrate! } + + expect(group_import_state.reload.user_id).to eq(user.id) + end + end + end + + def create_member(options) + members_table.create!( + { + notification_level: 0, + ldap: false, + override: false + }.merge(options) + ) + end +end diff --git a/spec/migrations/ensure_filled_file_store_on_package_files_spec.rb b/spec/migrations/ensure_filled_file_store_on_package_files_spec.rb new file mode 100644 index 00000000000..8a0f51ab27e --- /dev/null +++ b/spec/migrations/ensure_filled_file_store_on_package_files_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20200915185707_ensure_filled_file_store_on_package_files.rb') + +RSpec.describe EnsureFilledFileStoreOnPackageFiles, schema: 20200910175553 do + let!(:packages_package_files) { table(:packages_package_files) } + let!(:packages_packages) { table(:packages_packages) } + let!(:namespaces) { table(:namespaces) } + let!(:projects) { table(:projects) } + let!(:namespace) { namespaces.create!(name: 'foo', path: 'foo') } + let!(:project) { projects.create!(namespace_id: namespace.id) } + let!(:package) { packages_packages.create!(project_id: project.id, name: 'bar', package_type: 1) } + + before do + constraint_name = 'check_4c5e6bb0b3' + + # In order to insert a row with a NULL to fill. + ActiveRecord::Base.connection.execute "ALTER TABLE packages_package_files DROP CONSTRAINT #{constraint_name}" + + @file_store_1 = packages_package_files.create!(file_store: 1, file_name: 'foo_1', file: 'foo_1', package_id: package.id) + @file_store_2 = packages_package_files.create!(file_store: 2, file_name: 'foo_2', file: 'foo_2', package_id: package.id) + @file_store_nil = packages_package_files.create!(file_store: nil, file_name: 'foo_nil', file: 'foo_nil', package_id: package.id) + + # revert DB structure + ActiveRecord::Base.connection.execute "ALTER TABLE packages_package_files ADD CONSTRAINT #{constraint_name} CHECK ((file_store IS NOT NULL)) NOT VALID" + end + + it 'correctly migrates nil file_store to 1' do + migrate! + + @file_store_1.reload + @file_store_2.reload + @file_store_nil.reload + + expect(@file_store_1.file_store).to eq(1) # unchanged + expect(@file_store_2.file_store).to eq(2) # unchanged + expect(@file_store_nil.file_store).to eq(1) # nil => 1 + end +end diff --git a/spec/migrations/migrate_compliance_framework_enum_to_database_framework_record_spec.rb b/spec/migrations/migrate_compliance_framework_enum_to_database_framework_record_spec.rb new file mode 100644 index 00000000000..cd2ec81abb7 --- /dev/null +++ b/spec/migrations/migrate_compliance_framework_enum_to_database_framework_record_spec.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20201005094331_migrate_compliance_framework_enum_to_database_framework_record.rb') + +RSpec.describe MigrateComplianceFrameworkEnumToDatabaseFrameworkRecord, schema: 20201005092753 do + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + let(:project_compliance_framework_settings) { table(:project_compliance_framework_settings) } + let(:compliance_management_frameworks) { table(:compliance_management_frameworks) } + + let(:gdpr_framework) { 1 } + let(:sox_framework) { 5 } + + let!(:root_group) { namespaces.create!(type: 'Group', name: 'a', path: 'a') } + let!(:sub_group) { namespaces.create!(type: 'Group', name: 'b', path: 'b', parent_id: root_group.id) } + let!(:sub_sub_group) { namespaces.create!(type: 'Group', name: 'c', path: 'c', parent_id: sub_group.id) } + + let!(:namespace) { namespaces.create!(name: 'd', path: 'd') } + + let!(:project_on_root_level) { projects.create!(namespace_id: root_group.id) } + let!(:project_on_sub_sub_level_1) { projects.create!(namespace_id: sub_sub_group.id) } + let!(:project_on_sub_sub_level_2) { projects.create!(namespace_id: sub_sub_group.id) } + let!(:project_on_namespace) { projects.create!(namespace_id: namespace.id) } + + let!(:project_on_root_level_compliance_setting) { project_compliance_framework_settings.create!(project_id: project_on_root_level.id, framework: gdpr_framework) } + let!(:project_on_sub_sub_level_compliance_setting_1) { project_compliance_framework_settings.create!(project_id: project_on_sub_sub_level_1.id, framework: sox_framework) } + let!(:project_on_sub_sub_level_compliance_setting_2) { project_compliance_framework_settings.create!(project_id: project_on_sub_sub_level_2.id, framework: gdpr_framework) } + let!(:project_on_namespace_level_compliance_setting) { project_compliance_framework_settings.create!(project_id: project_on_namespace.id, framework: gdpr_framework) } + + subject { described_class.new.up } + + context 'when Gitlab.ee? is true' do + before do + expect(Gitlab).to receive(:ee?).and_return(true) + end + + it 'updates the project settings' do + subject + + gdpr_framework = compliance_management_frameworks.find_by(namespace_id: root_group.id, name: 'GDPR') + expect(project_on_root_level_compliance_setting.reload.framework_id).to eq(gdpr_framework.id) + expect(project_on_sub_sub_level_compliance_setting_2.reload.framework_id).to eq(gdpr_framework.id) + + sox_framework = compliance_management_frameworks.find_by(namespace_id: root_group.id, name: 'SOX') + expect(project_on_sub_sub_level_compliance_setting_1.reload.framework_id).to eq(sox_framework.id) + + gdpr_framework = compliance_management_frameworks.find_by(namespace_id: namespace.id, name: 'GDPR') + expect(project_on_namespace_level_compliance_setting.reload.framework_id).to eq(gdpr_framework.id) + end + + it 'adds two framework records' do + subject + + expect(compliance_management_frameworks.count).to eq(3) + end + end + + context 'when Gitlab.ee? is false' do + before do + expect(Gitlab).to receive(:ee?).and_return(false) + end + + it 'does nothing' do + subject + + expect(compliance_management_frameworks.count).to eq(0) + end + end +end diff --git a/spec/migrations/schedule_blocked_by_links_replacement_spec.rb b/spec/migrations/schedule_blocked_by_links_replacement_spec.rb new file mode 100644 index 00000000000..36610507921 --- /dev/null +++ b/spec/migrations/schedule_blocked_by_links_replacement_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20201015073808_schedule_blocked_by_links_replacement') + +RSpec.describe ScheduleBlockedByLinksReplacement do + let(:namespace) { table(:namespaces).create!(name: 'gitlab', path: 'gitlab-org') } + let(:project) { table(:projects).create!(namespace_id: namespace.id, name: 'gitlab') } + let(:issue1) { table(:issues).create!(project_id: project.id, title: 'a') } + let(:issue2) { table(:issues).create!(project_id: project.id, title: 'b') } + let(:issue3) { table(:issues).create!(project_id: project.id, title: 'c') } + let!(:issue_links) do + [ + table(:issue_links).create!(source_id: issue1.id, target_id: issue2.id, link_type: 1), + table(:issue_links).create!(source_id: issue2.id, target_id: issue1.id, link_type: 2), + table(:issue_links).create!(source_id: issue1.id, target_id: issue3.id, link_type: 2) + ] + end + + before do + stub_const("#{described_class.name}::BATCH_SIZE", 1) + end + + it 'schedules jobs for blocked_by links' do + Sidekiq::Testing.fake! do + freeze_time do + migrate! + + expect(described_class::MIGRATION).to be_scheduled_delayed_migration( + 2.minutes, issue_links[1].id, issue_links[1].id) + expect(described_class::MIGRATION).to be_scheduled_delayed_migration( + 4.minutes, issue_links[2].id, issue_links[2].id) + expect(BackgroundMigrationWorker.jobs.size).to eq(2) + end + end + end +end diff --git a/spec/migrations/schedule_migrate_u2f_webauthn_spec.rb b/spec/migrations/schedule_migrate_u2f_webauthn_spec.rb new file mode 100644 index 00000000000..5dc4d676063 --- /dev/null +++ b/spec/migrations/schedule_migrate_u2f_webauthn_spec.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20200929114107_schedule_migrate_u2f_webauthn.rb') + +RSpec.describe ScheduleMigrateU2fWebauthn do + let(:migration_name) { described_class::MIGRATION } + let(:u2f_registrations) { table(:u2f_registrations) } + let(:webauthn_registrations) { table(:webauthn_registrations) } + + let(:users) { table(:users) } + + let(:user) { users.create!(email: 'email@email.com', name: 'foo', username: 'foo', projects_limit: 0) } + + before do + stub_const("#{described_class.name}::BATCH_SIZE", 1) + end + + context 'when there are u2f registrations' do + let!(:u2f_reg_1) { create_u2f_registration(1, 'reg1') } + let!(:u2f_reg_2) { create_u2f_registration(2, 'reg2') } + + it 'schedules a background migration' do + Sidekiq::Testing.fake! do + freeze_time do + migrate! + + expect(migration_name).to be_scheduled_delayed_migration(2.minutes, 1, 1) + expect(migration_name).to be_scheduled_delayed_migration(4.minutes, 2, 2) + expect(BackgroundMigrationWorker.jobs.size).to eq(2) + end + end + end + end + + context 'when there are no u2f registrations' do + it 'does not schedule background migrations' do + Sidekiq::Testing.fake! do + freeze_time do + migrate! + + expect(BackgroundMigrationWorker.jobs.size).to eq(0) + end + end + end + end + + def create_u2f_registration(id, name) + device = U2F::FakeU2F.new(FFaker::BaconIpsum.characters(5)) + u2f_registrations.create!({ id: id, + 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), + counter: 5, + name: name, + user_id: user.id }) + end +end diff --git a/spec/migrations/set_job_waiter_ttl_spec.rb b/spec/migrations/set_job_waiter_ttl_spec.rb new file mode 100644 index 00000000000..b9cf7c55798 --- /dev/null +++ b/spec/migrations/set_job_waiter_ttl_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20200930144340_set_job_waiter_ttl.rb') + +RSpec.describe SetJobWaiterTtl, :redis do + it 'sets TTLs where necessary' do + waiter_with_ttl = Gitlab::JobWaiter.new.key + waiter_without_ttl = Gitlab::JobWaiter.new.key + key_with_ttl = "foo:bar" + key_without_ttl = "foo:qux" + + Gitlab::Redis::SharedState.with do |redis| + redis.set(waiter_with_ttl, "zzz", ex: 2000) + redis.set(waiter_without_ttl, "zzz") + redis.set(key_with_ttl, "zzz", ex: 2000) + redis.set(key_without_ttl, "zzz") + + described_class.new.up + + # This is the point of the migration. We know the migration uses a TTL of 21_600 + expect(redis.ttl(waiter_without_ttl)).to be > 20_000 + + # Other TTL's should be untouched by the migration + expect(redis.ttl(waiter_with_ttl)).to be_between(1000, 2000) + expect(redis.ttl(key_with_ttl)).to be_between(1000, 2000) + expect(redis.ttl(key_without_ttl)).to eq(-1) + end + end +end |