diff options
Diffstat (limited to 'spec/migrations')
17 files changed, 815 insertions, 19 deletions
diff --git a/spec/migrations/20190924152703_migrate_issue_trackers_data_spec.rb b/spec/migrations/20190924152703_migrate_issue_trackers_data_spec.rb index 2999332509a..dad95760306 100644 --- a/spec/migrations/20190924152703_migrate_issue_trackers_data_spec.rb +++ b/spec/migrations/20190924152703_migrate_issue_trackers_data_spec.rb @@ -14,11 +14,11 @@ RSpec.describe MigrateIssueTrackersData do } end - let!(:jira_service) do + let!(:jira_integration) do services.create!(type: 'JiraService', properties: properties, category: 'issue_tracker') end - let!(:jira_service_nil) do + let!(:jira_integration_nil) do services.create!(type: 'JiraService', properties: nil, category: 'issue_tracker') end @@ -26,11 +26,11 @@ RSpec.describe MigrateIssueTrackersData do services.create!(type: 'BugzillaService', properties: properties, category: 'issue_tracker') end - let!(:youtrack_service) do + let!(:youtrack_integration) do services.create!(type: 'YoutrackService', properties: properties, category: 'issue_tracker') end - let!(:youtrack_service_empty) do + let!(:youtrack_integration_empty) do services.create!(type: 'YoutrackService', properties: '', category: 'issue_tracker') end @@ -55,8 +55,8 @@ RSpec.describe MigrateIssueTrackersData do freeze_time do migrate! - expect(migration_name).to be_scheduled_delayed_migration(3.minutes, jira_service.id, bugzilla_integration.id) - expect(migration_name).to be_scheduled_delayed_migration(6.minutes, youtrack_service.id, gitlab_service.id) + expect(migration_name).to be_scheduled_delayed_migration(3.minutes, jira_integration.id, bugzilla_integration.id) + expect(migration_name).to be_scheduled_delayed_migration(6.minutes, youtrack_integration.id, gitlab_service.id) expect(BackgroundMigrationWorker.jobs.size).to eq(2) end end diff --git a/spec/migrations/20200130145430_reschedule_migrate_issue_trackers_data_spec.rb b/spec/migrations/20200130145430_reschedule_migrate_issue_trackers_data_spec.rb index 5516e2af3f1..cf8bc608483 100644 --- a/spec/migrations/20200130145430_reschedule_migrate_issue_trackers_data_spec.rb +++ b/spec/migrations/20200130145430_reschedule_migrate_issue_trackers_data_spec.rb @@ -14,11 +14,11 @@ RSpec.describe RescheduleMigrateIssueTrackersData do } end - let!(:jira_service) do + let!(:jira_integration) do services.create!(id: 10, type: 'JiraService', properties: properties, category: 'issue_tracker') end - let!(:jira_service_nil) do + let!(:jira_integration_nil) do services.create!(id: 11, type: 'JiraService', properties: nil, category: 'issue_tracker') end @@ -26,11 +26,11 @@ RSpec.describe RescheduleMigrateIssueTrackersData do services.create!(id: 12, type: 'BugzillaService', properties: properties, category: 'issue_tracker') end - let!(:youtrack_service) do + let!(:youtrack_integration) do services.create!(id: 13, type: 'YoutrackService', properties: properties, category: 'issue_tracker') end - let!(:youtrack_service_empty) do + let!(:youtrack_integration_empty) do services.create!(id: 14, type: 'YoutrackService', properties: '', category: 'issue_tracker') end @@ -56,8 +56,8 @@ RSpec.describe RescheduleMigrateIssueTrackersData do freeze_time do migrate! - expect(migration_name).to be_scheduled_delayed_migration(3.minutes, jira_service.id, bugzilla_integration.id) - expect(migration_name).to be_scheduled_delayed_migration(6.minutes, youtrack_service.id, gitlab_service.id) + expect(migration_name).to be_scheduled_delayed_migration(3.minutes, jira_integration.id, bugzilla_integration.id) + expect(migration_name).to be_scheduled_delayed_migration(6.minutes, youtrack_integration.id, gitlab_service.id) expect(BackgroundMigrationWorker.jobs.size).to eq(2) end end diff --git a/spec/migrations/20200728080250_replace_unique_index_on_cycle_analytics_stages_spec.rb b/spec/migrations/20200728080250_replace_unique_index_on_cycle_analytics_stages_spec.rb index 761168ae609..a632065946d 100644 --- a/spec/migrations/20200728080250_replace_unique_index_on_cycle_analytics_stages_spec.rb +++ b/spec/migrations/20200728080250_replace_unique_index_on_cycle_analytics_stages_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' require_migration!('replace_unique_index_on_cycle_analytics_stages') -RSpec.describe ReplaceUniqueIndexOnCycleAnalyticsStages, :migration, schema: 20200728080250 do +RSpec.describe ReplaceUniqueIndexOnCycleAnalyticsStages, :migration, schema: 20200727142337 do let(:namespaces) { table(:namespaces) } let(:group_value_streams) { table(:analytics_cycle_analytics_group_value_streams) } let(:group_stages) { table(:analytics_cycle_analytics_group_stages) } diff --git a/spec/migrations/20210610153556_delete_legacy_operations_feature_flags_spec.rb b/spec/migrations/20210610153556_delete_legacy_operations_feature_flags_spec.rb new file mode 100644 index 00000000000..4f621d0670c --- /dev/null +++ b/spec/migrations/20210610153556_delete_legacy_operations_feature_flags_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require 'spec_helper' + +require_migration!('delete_legacy_operations_feature_flags') + +RSpec.describe DeleteLegacyOperationsFeatureFlags do + let(:namespace) { table(:namespaces).create!(name: 'foo', path: 'bar') } + let(:project) { table(:projects).create!(namespace_id: namespace.id) } + let(:issue) { table(:issues).create!(id: 123, project_id: project.id) } + let(:operations_feature_flags) { table(:operations_feature_flags) } + let(:operations_feature_flag_scopes) { table(:operations_feature_flag_scopes) } + let(:operations_strategies) { table(:operations_strategies) } + let(:operations_scopes) { table(:operations_scopes) } + let(:operations_feature_flags_issues) { table(:operations_feature_flags_issues) } + + it 'correctly deletes legacy feature flags' do + # Legacy version of a feature flag - dropped support in GitLab 14.0. + legacy_flag = operations_feature_flags.create!(project_id: project.id, version: 1, name: 'flag_a', active: true, iid: 1) + operations_feature_flag_scopes.create!(feature_flag_id: legacy_flag.id, active: true) + operations_feature_flags_issues.create!(feature_flag_id: legacy_flag.id, issue_id: issue.id) + # New version of a feature flag. + new_flag = operations_feature_flags.create!(project_id: project.id, version: 2, name: 'flag_b', active: true, iid: 2) + new_strategy = operations_strategies.create!(feature_flag_id: new_flag.id, name: 'default') + operations_scopes.create!(strategy_id: new_strategy.id, environment_scope: '*') + operations_feature_flags_issues.create!(feature_flag_id: new_flag.id, issue_id: issue.id) + + expect(operations_feature_flags.all.pluck(:version)).to contain_exactly(1, 2) + expect(operations_feature_flag_scopes.count).to eq(1) + expect(operations_strategies.count).to eq(1) + expect(operations_scopes.count).to eq(1) + expect(operations_feature_flags_issues.all.pluck(:feature_flag_id)).to contain_exactly(legacy_flag.id, new_flag.id) + + migrate! + + # Legacy flag is deleted. + expect(operations_feature_flags.all.pluck(:version)).to contain_exactly(2) + # The associated entries of the legacy flag are deleted too. + expect(operations_feature_flag_scopes.count).to eq(0) + # The associated entries of the new flag stay instact. + expect(operations_strategies.count).to eq(1) + expect(operations_scopes.count).to eq(1) + expect(operations_feature_flags_issues.all.pluck(:feature_flag_id)).to contain_exactly(new_flag.id) + end +end diff --git a/spec/migrations/2021061716138_cascade_delete_freeze_periods_spec.rb b/spec/migrations/2021061716138_cascade_delete_freeze_periods_spec.rb new file mode 100644 index 00000000000..fd664d99f06 --- /dev/null +++ b/spec/migrations/2021061716138_cascade_delete_freeze_periods_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require 'spec_helper' + +require_migration!('cascade_delete_freeze_periods') + +RSpec.describe CascadeDeleteFreezePeriods do + let(:namespace) { table(:namespaces).create!(name: 'deploy_freeze', path: 'deploy_freeze') } + let(:project) { table(:projects).create!(id: 1, namespace_id: namespace.id) } + let(:freeze_periods) { table(:ci_freeze_periods) } + + describe "#up" do + it 'allows for a project to be deleted' do + freeze_periods.create!(id: 1, project_id: project.id, freeze_start: '5 * * * *', freeze_end: '6 * * * *', cron_timezone: 'UTC') + migrate! + + project.delete + + expect(freeze_periods.where(project_id: project.id).count).to be_zero + end + end +end diff --git a/spec/migrations/20210708130419_reschedule_merge_request_diff_users_background_migration_spec.rb b/spec/migrations/20210708130419_reschedule_merge_request_diff_users_background_migration_spec.rb new file mode 100644 index 00000000000..9cc454662f9 --- /dev/null +++ b/spec/migrations/20210708130419_reschedule_merge_request_diff_users_background_migration_spec.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! 'reschedule_merge_request_diff_users_background_migration' + +RSpec.describe RescheduleMergeRequestDiffUsersBackgroundMigration, :migration do + let(:migration) { described_class.new } + + describe '#up' do + before do + allow(described_class::MergeRequestDiff) + .to receive(:minimum) + .with(:id) + .and_return(42) + + allow(described_class::MergeRequestDiff) + .to receive(:maximum) + .with(:id) + .and_return(85_123) + end + + it 'deletes existing background migration job records' do + args = [150_000, 300_000] + + Gitlab::Database::BackgroundMigrationJob + .create!(class_name: described_class::MIGRATION_NAME, arguments: args) + + migration.up + + found = Gitlab::Database::BackgroundMigrationJob + .where(class_name: described_class::MIGRATION_NAME, arguments: args) + .count + + expect(found).to eq(0) + end + + it 'schedules the migrations in batches' do + expect(migration) + .to receive(:migrate_in) + .ordered + .with(2.minutes.to_i, described_class::MIGRATION_NAME, [42, 40_042]) + + expect(migration) + .to receive(:migrate_in) + .ordered + .with(4.minutes.to_i, described_class::MIGRATION_NAME, [40_042, 80_042]) + + expect(migration) + .to receive(:migrate_in) + .ordered + .with(6.minutes.to_i, described_class::MIGRATION_NAME, [80_042, 120_042]) + + migration.up + end + + it 'creates rows to track the background migration jobs' do + expect(Gitlab::Database::BackgroundMigrationJob) + .to receive(:create!) + .ordered + .with(class_name: described_class::MIGRATION_NAME, arguments: [42, 40_042]) + + expect(Gitlab::Database::BackgroundMigrationJob) + .to receive(:create!) + .ordered + .with(class_name: described_class::MIGRATION_NAME, arguments: [40_042, 80_042]) + + expect(Gitlab::Database::BackgroundMigrationJob) + .to receive(:create!) + .ordered + .with(class_name: described_class::MIGRATION_NAME, arguments: [80_042, 120_042]) + + migration.up + end + end +end diff --git a/spec/migrations/active_record/schema_spec.rb b/spec/migrations/active_record/schema_spec.rb index 8199f55f5fc..4a505c51a16 100644 --- a/spec/migrations/active_record/schema_spec.rb +++ b/spec/migrations/active_record/schema_spec.rb @@ -7,10 +7,10 @@ require 'spec_helper' RSpec.describe ActiveRecord::Schema, schema: :latest do let(:all_migrations) do - migrations_paths = %w[db/migrate db/post_migrate] - .map { |path| Rails.root.join(*path, '*') } + migrations_directories = %w[db/migrate db/post_migrate].map { |path| Rails.root.join(path).to_s } + migrations_paths = migrations_directories.map { |path| File.join(path, '*') } - migrations = Dir[*migrations_paths] + migrations = Dir[*migrations_paths] - migrations_directories migrations.map { |migration| File.basename(migration).split('_').first.to_i }.sort end diff --git a/spec/migrations/add_premium_and_ultimate_plan_limits_spec.rb b/spec/migrations/add_premium_and_ultimate_plan_limits_spec.rb new file mode 100644 index 00000000000..fb62fc3ca02 --- /dev/null +++ b/spec/migrations/add_premium_and_ultimate_plan_limits_spec.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +require 'spec_helper' + +require_migration! + +RSpec.describe AddPremiumAndUltimatePlanLimits, :migration do + shared_examples_for 'a migration that does not alter plans or plan limits' do + it do + expect { migrate! }.not_to change { + [ + AddPremiumAndUltimatePlanLimits::Plan.count, + AddPremiumAndUltimatePlanLimits::PlanLimits.count + ] + } + end + end + + describe '#up' do + context 'when not .com?' do + before do + allow(Gitlab).to receive(:com?).and_return false + end + + it_behaves_like 'a migration that does not alter plans or plan limits' + end + + context 'when .com?' do + before do + allow(Gitlab).to receive(:com?).and_return true + end + + context 'when source plan does not exist' do + it_behaves_like 'a migration that does not alter plans or plan limits' + end + + context 'when target plan does not exist' do + before do + table(:plans).create!(name: 'silver', title: 'Silver') + table(:plans).create!(name: 'gold', title: 'Gold') + end + + it_behaves_like 'a migration that does not alter plans or plan limits' + end + + context 'when source and target plans exist' do + let!(:silver) { table(:plans).create!(name: 'silver', title: 'Silver') } + let!(:gold) { table(:plans).create!(name: 'gold', title: 'Gold') } + let!(:premium) { table(:plans).create!(name: 'premium', title: 'Premium') } + let!(:ultimate) { table(:plans).create!(name: 'ultimate', title: 'Ultimate') } + + let!(:silver_limits) { table(:plan_limits).create!(plan_id: silver.id, storage_size_limit: 111) } + let!(:gold_limits) { table(:plan_limits).create!(plan_id: gold.id, storage_size_limit: 222) } + + context 'when target has plan limits' do + before do + table(:plan_limits).create!(plan_id: premium.id, storage_size_limit: 999) + table(:plan_limits).create!(plan_id: ultimate.id, storage_size_limit: 999) + end + + it 'does not overwrite the limits' do + expect { migrate! }.not_to change { + [ + AddPremiumAndUltimatePlanLimits::Plan.count, + AddPremiumAndUltimatePlanLimits::PlanLimits.pluck(:id, :storage_size_limit).sort + ] + } + end + end + + context 'when target has no plan limits' do + it 'creates plan limits from the source plan' do + migrate! + + expect(AddPremiumAndUltimatePlanLimits::PlanLimits.pluck(:plan_id, :storage_size_limit)).to match_array([ + [silver.id, silver_limits.storage_size_limit], + [gold.id, gold_limits.storage_size_limit], + [premium.id, silver_limits.storage_size_limit], + [ultimate.id, gold_limits.storage_size_limit] + ]) + end + end + end + end + end +end diff --git a/spec/migrations/add_upvotes_count_index_to_issues_spec.rb b/spec/migrations/add_upvotes_count_index_to_issues_spec.rb new file mode 100644 index 00000000000..c04cb98a107 --- /dev/null +++ b/spec/migrations/add_upvotes_count_index_to_issues_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe AddUpvotesCountIndexToIssues do + let(:migration_instance) { described_class.new } + + describe '#up' do + it 'adds index' do + expect { migrate! }.to change { migration_instance.index_exists?(:issues, [:project_id, :upvotes_count], name: described_class::INDEX_NAME) }.from(false).to(true) + end + end + + describe '#down' do + it 'removes index' do + migrate! + + expect { schema_migrate_down! }.to change { migration_instance.index_exists?(:issues, [:project_id, :upvotes_count], name: described_class::INDEX_NAME) }.from(true).to(false) + end + end +end diff --git a/spec/migrations/backfill_issues_upvotes_count_spec.rb b/spec/migrations/backfill_issues_upvotes_count_spec.rb new file mode 100644 index 00000000000..f2bea0edea0 --- /dev/null +++ b/spec/migrations/backfill_issues_upvotes_count_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe BackfillIssuesUpvotesCount do + let(:migration) { described_class.new } + let(:issues) { table(:issues) } + let(:award_emoji) { table(:award_emoji) } + + let!(:issue1) { issues.create! } + let!(:issue2) { issues.create! } + let!(:issue3) { issues.create! } + let!(:issue4) { issues.create! } + let!(:issue4_without_thumbsup) { issues.create! } + + let!(:award_emoji1) { award_emoji.create!( name: 'thumbsup', awardable_type: 'Issue', awardable_id: issue1.id) } + let!(:award_emoji2) { award_emoji.create!( name: 'thumbsup', awardable_type: 'Issue', awardable_id: issue2.id) } + let!(:award_emoji3) { award_emoji.create!( name: 'thumbsup', awardable_type: 'Issue', awardable_id: issue3.id) } + let!(:award_emoji4) { award_emoji.create!( name: 'thumbsup', awardable_type: 'Issue', awardable_id: issue4.id) } + + it 'correctly schedules background migrations' 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(issue1.id, issue2.id) + expect(described_class::MIGRATION).to be_scheduled_migration(issue3.id, issue4.id) + expect(BackgroundMigrationWorker.jobs.size).to eq(2) + end + end + end +end diff --git a/spec/migrations/delete_template_services_duplicated_by_type_spec.rb b/spec/migrations/delete_template_services_duplicated_by_type_spec.rb index b5a29436159..577fea984da 100644 --- a/spec/migrations/delete_template_services_duplicated_by_type_spec.rb +++ b/spec/migrations/delete_template_services_duplicated_by_type_spec.rb @@ -14,11 +14,11 @@ RSpec.describe DeleteTemplateServicesDuplicatedByType do end it 'deletes service templates duplicated by type except the one with the lowest ID' do - jenkins_service_id = services.where(type: 'JenkinsService').order(:id).pluck(:id).first - jira_service_id = services.where(type: 'JiraService').pluck(:id).first + jenkins_integration_id = services.where(type: 'JenkinsService').order(:id).pluck(:id).first + jira_integration_id = services.where(type: 'JiraService').pluck(:id).first migrate! - expect(services.pluck(:id)).to contain_exactly(jenkins_service_id, jira_service_id) + expect(services.pluck(:id)).to contain_exactly(jenkins_integration_id, jira_integration_id) end end diff --git a/spec/migrations/fix_batched_migrations_old_format_job_arguments_spec.rb b/spec/migrations/fix_batched_migrations_old_format_job_arguments_spec.rb new file mode 100644 index 00000000000..e15011d0dab --- /dev/null +++ b/spec/migrations/fix_batched_migrations_old_format_job_arguments_spec.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +# rubocop:disable Style/WordArray +RSpec.describe FixBatchedMigrationsOldFormatJobArguments do + let(:batched_background_migrations) { table(:batched_background_migrations) } + + context 'when migrations with legacy job arguments exists' do + it 'updates job arguments to current format' do + legacy_events_migration = create_batched_migration('events', 'id', ['id', 'id_convert_to_bigint']) + legacy_push_event_payloads_migration = create_batched_migration('push_event_payloads', 'event_id', ['event_id', 'event_id_convert_to_bigint']) + + migrate! + + expect(legacy_events_migration.reload.job_arguments).to eq([['id'], ['id_convert_to_bigint']]) + expect(legacy_push_event_payloads_migration.reload.job_arguments).to eq([['event_id'], ['event_id_convert_to_bigint']]) + end + end + + context 'when only migrations with current job arguments exists' do + it 'updates nothing' do + events_migration = create_batched_migration('events', 'id', [['id'], ['id_convert_to_bigint']]) + push_event_payloads_migration = create_batched_migration('push_event_payloads', 'event_id', [['event_id'], ['event_id_convert_to_bigint']]) + + migrate! + + expect(events_migration.reload.job_arguments).to eq([['id'], ['id_convert_to_bigint']]) + expect(push_event_payloads_migration.reload.job_arguments).to eq([['event_id'], ['event_id_convert_to_bigint']]) + end + end + + context 'when migrations with both legacy and current job arguments exist' do + it 'updates nothing' do + legacy_events_migration = create_batched_migration('events', 'id', ['id', 'id_convert_to_bigint']) + events_migration = create_batched_migration('events', 'id', [['id'], ['id_convert_to_bigint']]) + legacy_push_event_payloads_migration = create_batched_migration('push_event_payloads', 'event_id', ['event_id', 'event_id_convert_to_bigint']) + push_event_payloads_migration = create_batched_migration('push_event_payloads', 'event_id', [['event_id'], ['event_id_convert_to_bigint']]) + + migrate! + + expect(legacy_events_migration.reload.job_arguments).to eq(['id', 'id_convert_to_bigint']) + expect(events_migration.reload.job_arguments).to eq([['id'], ['id_convert_to_bigint']]) + expect(legacy_push_event_payloads_migration.reload.job_arguments).to eq(['event_id', 'event_id_convert_to_bigint']) + expect(push_event_payloads_migration.reload.job_arguments).to eq([['event_id'], ['event_id_convert_to_bigint']]) + end + end + + def create_batched_migration(table_name, column_name, job_arguments) + batched_background_migrations.create!( + max_value: 10, + batch_size: 10, + sub_batch_size: 10, + interval: 1, + job_class_name: 'CopyColumnUsingBackgroundMigrationJob', + table_name: table_name, + column_name: column_name, + job_arguments: job_arguments + ) + end +end +# rubocop:enable Style/WordArray diff --git a/spec/migrations/re_schedule_latest_pipeline_id_population_spec.rb b/spec/migrations/re_schedule_latest_pipeline_id_population_spec.rb new file mode 100644 index 00000000000..354a0896ac9 --- /dev/null +++ b/spec/migrations/re_schedule_latest_pipeline_id_population_spec.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe ReScheduleLatestPipelineIdPopulation do + let(:namespaces) { table(:namespaces) } + let(:pipelines) { table(:ci_pipelines) } + let(:projects) { table(:projects) } + let(:project_settings) { table(:project_settings) } + let(:vulnerability_statistics) { table(:vulnerability_statistics) } + + let(:letter_grade_a) { 0 } + + let(:namespace) { namespaces.create!(name: 'gitlab', path: 'gitlab-org') } + let(:project_1) { projects.create!(namespace_id: namespace.id, name: 'Foo 1') } + let(:project_2) { projects.create!(namespace_id: namespace.id, name: 'Foo 2') } + let(:project_3) { projects.create!(namespace_id: namespace.id, name: 'Foo 3') } + let(:project_4) { projects.create!(namespace_id: namespace.id, name: 'Foo 4') } + + before do + project_settings.create!(project_id: project_1.id, has_vulnerabilities: true) + project_settings.create!(project_id: project_2.id, has_vulnerabilities: true) + project_settings.create!(project_id: project_3.id) + project_settings.create!(project_id: project_4.id, has_vulnerabilities: true) + + pipeline = pipelines.create!(project_id: project_2.id, ref: 'master', sha: 'adf43c3a') + + vulnerability_statistics.create!(project_id: project_2.id, letter_grade: letter_grade_a, latest_pipeline_id: pipeline.id) + vulnerability_statistics.create!(project_id: project_4.id, letter_grade: letter_grade_a) + + allow(Gitlab).to receive(:ee?).and_return(is_ee?) + stub_const("#{described_class.name}::BATCH_SIZE", 1) + end + + around do |example| + freeze_time { example.run } + end + + context 'when the installation is FOSS' do + let(:is_ee?) { false } + + it 'does not schedule any background job' do + migrate! + + expect(BackgroundMigrationWorker.jobs.size).to be(0) + end + end + + context 'when the installation is EE' do + let(:is_ee?) { true } + + it 'schedules the background jobs' do + migrate! + + expect(BackgroundMigrationWorker.jobs.size).to be(2) + expect(described_class::MIGRATION).to be_scheduled_delayed_migration(described_class::DELAY_INTERVAL, project_1.id, project_1.id) + expect(described_class::MIGRATION).to be_scheduled_delayed_migration(2 * described_class::DELAY_INTERVAL, project_4.id, project_4.id) + end + end +end diff --git a/spec/migrations/rename_services_to_integrations_spec.rb b/spec/migrations/rename_services_to_integrations_spec.rb new file mode 100644 index 00000000000..812dd5efecb --- /dev/null +++ b/spec/migrations/rename_services_to_integrations_spec.rb @@ -0,0 +1,255 @@ +# frozen_string_literal: true + +require 'spec_helper' + +require_migration! + +RSpec.describe RenameServicesToIntegrations do + let(:migration) { described_class.new } + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + let(:integrations) { table(:integrations) } + let(:services) { table(:services) } + + before do + @namespace = namespaces.create!(name: 'foo', path: 'foo') + @project = projects.create!(namespace_id: @namespace.id) + end + + RSpec.shared_examples 'a table (or view) with triggers' do + describe 'INSERT tracker trigger' do + it 'sets `has_external_issue_tracker` to true when active `issue_tracker` is inserted' do + expect do + subject.create!(category: 'issue_tracker', active: true, project_id: @project.id) + end.to change { @project.reload.has_external_issue_tracker }.to(true) + end + + it 'does not set `has_external_issue_tracker` to true when integration is for a different project' do + different_project = projects.create!(namespace_id: @namespace.id) + + expect do + subject.create!(category: 'issue_tracker', active: true, project_id: different_project.id) + end.not_to change { @project.reload.has_external_issue_tracker } + end + + it 'does not set `has_external_issue_tracker` to true when inactive `issue_tracker` is inserted' do + expect do + subject.create!(category: 'issue_tracker', active: false, project_id: @project.id) + end.not_to change { @project.reload.has_external_issue_tracker } + end + + it 'does not set `has_external_issue_tracker` to true when a non-`issue tracker` active integration is inserted' do + expect do + subject.create!(category: 'my_type', active: true, project_id: @project.id) + end.not_to change { @project.reload.has_external_issue_tracker } + end + end + + describe 'UPDATE tracker trigger' do + it 'sets `has_external_issue_tracker` to true when `issue_tracker` is made active' do + integration = subject.create!(category: 'issue_tracker', active: false, project_id: @project.id) + + expect do + integration.update!(active: true) + end.to change { @project.reload.has_external_issue_tracker }.to(true) + end + + it 'sets `has_external_issue_tracker` to false when `issue_tracker` is made inactive' do + integration = subject.create!(category: 'issue_tracker', active: true, project_id: @project.id) + + expect do + integration.update!(active: false) + end.to change { @project.reload.has_external_issue_tracker }.to(false) + end + + it 'sets `has_external_issue_tracker` to false when `issue_tracker` is made inactive, and an inactive `issue_tracker` exists' do + subject.create!(category: 'issue_tracker', active: false, project_id: @project.id) + integration = subject.create!(category: 'issue_tracker', active: true, project_id: @project.id) + + expect do + integration.update!(active: false) + end.to change { @project.reload.has_external_issue_tracker }.to(false) + end + + it 'does not change `has_external_issue_tracker` when `issue_tracker` is made inactive, if an active `issue_tracker` exists' do + subject.create!(category: 'issue_tracker', active: true, project_id: @project.id) + integration = subject.create!(category: 'issue_tracker', active: true, project_id: @project.id) + + expect do + integration.update!(active: false) + end.not_to change { @project.reload.has_external_issue_tracker } + end + + it 'does not change `has_external_issue_tracker` when integration is for a different project' do + different_project = projects.create!(namespace_id: @namespace.id) + integration = subject.create!(category: 'issue_tracker', active: false, project_id: different_project.id) + + expect do + integration.update!(active: true) + end.not_to change { @project.reload.has_external_issue_tracker } + end + end + + describe 'DELETE tracker trigger' do + it 'sets `has_external_issue_tracker` to false when `issue_tracker` is deleted' do + integration = subject.create!(category: 'issue_tracker', active: true, project_id: @project.id) + + expect do + integration.delete + end.to change { @project.reload.has_external_issue_tracker }.to(false) + end + + it 'sets `has_external_issue_tracker` to false when `issue_tracker` is deleted, if an inactive `issue_tracker` still exists' do + subject.create!(category: 'issue_tracker', active: false, project_id: @project.id) + integration = subject.create!(category: 'issue_tracker', active: true, project_id: @project.id) + + expect do + integration.delete + end.to change { @project.reload.has_external_issue_tracker }.to(false) + end + + it 'does not change `has_external_issue_tracker` when `issue_tracker` is deleted, if an active `issue_tracker` still exists' do + subject.create!(category: 'issue_tracker', active: true, project_id: @project.id) + integration = subject.create!(category: 'issue_tracker', active: true, project_id: @project.id) + + expect do + integration.delete + end.not_to change { @project.reload.has_external_issue_tracker } + end + + it 'does not change `has_external_issue_tracker` when integration is for a different project' do + different_project = projects.create!(namespace_id: @namespace.id) + integration = subject.create!(category: 'issue_tracker', active: true, project_id: different_project.id) + + expect do + integration.delete + end.not_to change { @project.reload.has_external_issue_tracker } + end + end + + describe 'INSERT wiki trigger' do + it 'sets `has_external_wiki` to true when active `ExternalWikiService` is inserted' do + expect do + subject.create!(type: 'ExternalWikiService', active: true, project_id: @project.id) + end.to change { @project.reload.has_external_wiki }.to(true) + end + + it 'does not set `has_external_wiki` to true when integration is for a different project' do + different_project = projects.create!(namespace_id: @namespace.id) + + expect do + subject.create!(type: 'ExternalWikiService', active: true, project_id: different_project.id) + end.not_to change { @project.reload.has_external_wiki } + end + + it 'does not set `has_external_wiki` to true when inactive `ExternalWikiService` is inserted' do + expect do + subject.create!(type: 'ExternalWikiService', active: false, project_id: @project.id) + end.not_to change { @project.reload.has_external_wiki } + end + + it 'does not set `has_external_wiki` to true when active other integration is inserted' do + expect do + subject.create!(type: 'MyService', active: true, project_id: @project.id) + end.not_to change { @project.reload.has_external_wiki } + end + end + + describe 'UPDATE wiki trigger' do + it 'sets `has_external_wiki` to true when `ExternalWikiService` is made active' do + integration = subject.create!(type: 'ExternalWikiService', active: false, project_id: @project.id) + + expect do + integration.update!(active: true) + end.to change { @project.reload.has_external_wiki }.to(true) + end + + it 'sets `has_external_wiki` to false when `ExternalWikiService` is made inactive' do + integration = subject.create!(type: 'ExternalWikiService', active: true, project_id: @project.id) + + expect do + integration.update!(active: false) + end.to change { @project.reload.has_external_wiki }.to(false) + end + + it 'does not change `has_external_wiki` when integration is for a different project' do + different_project = projects.create!(namespace_id: @namespace.id) + integration = subject.create!(type: 'ExternalWikiService', active: false, project_id: different_project.id) + + expect do + integration.update!(active: true) + end.not_to change { @project.reload.has_external_wiki } + end + end + + describe 'DELETE wiki trigger' do + it 'sets `has_external_wiki` to false when `ExternalWikiService` is deleted' do + integration = subject.create!(type: 'ExternalWikiService', active: true, project_id: @project.id) + + expect do + integration.delete + end.to change { @project.reload.has_external_wiki }.to(false) + end + + it 'does not change `has_external_wiki` when integration is for a different project' do + different_project = projects.create!(namespace_id: @namespace.id) + integration = subject.create!(type: 'ExternalWikiService', active: true, project_id: different_project.id) + + expect do + integration.delete + end.not_to change { @project.reload.has_external_wiki } + end + end + end + + RSpec.shared_examples 'a table (or view) without triggers' do + specify do + number_of_triggers = ActiveRecord::Base.connection + .execute("SELECT count(*) FROM information_schema.triggers WHERE event_object_table = '#{subject.table_name}'") + .first['count'] + + expect(number_of_triggers).to eq(0) + end + end + + describe '#up' do + before do + # LOCK TABLE statements must be in a transaction + ActiveRecord::Base.transaction { migrate! } + end + + context 'the integrations table' do + subject { integrations } + + it_behaves_like 'a table (or view) with triggers' + end + + context 'the services table' do + subject { services } + + it_behaves_like 'a table (or view) without triggers' + end + end + + describe '#down' do + before do + # LOCK TABLE statements must be in a transaction + ActiveRecord::Base.transaction do + migration.up + migration.down + end + end + + context 'the services table' do + subject { services } + + it_behaves_like 'a table (or view) with triggers' + end + + context 'the integrations table' do + subject { integrations } + + it_behaves_like 'a table (or view) without triggers' + end + end +end diff --git a/spec/migrations/reset_job_token_scope_enabled_spec.rb b/spec/migrations/reset_job_token_scope_enabled_spec.rb new file mode 100644 index 00000000000..40dfe4de34b --- /dev/null +++ b/spec/migrations/reset_job_token_scope_enabled_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require 'spec_helper' + +require_migration! + +RSpec.describe ResetJobTokenScopeEnabled do + let(:settings) { table(:project_ci_cd_settings) } + let(:projects) { table(:projects) } + let(:namespaces) { table(:namespaces) } + let(:namespace) { namespaces.create!(name: 'gitlab', path: 'gitlab-org') } + let(:project_1) { projects.create!(name: 'proj-1', path: 'gitlab-org', namespace_id: namespace.id)} + let(:project_2) { projects.create!(name: 'proj-2', path: 'gitlab-org', namespace_id: namespace.id)} + + before do + settings.create!(id: 1, project_id: project_1.id, job_token_scope_enabled: true) + settings.create!(id: 2, project_id: project_2.id, job_token_scope_enabled: false) + end + + it 'migrates job_token_scope_enabled to be always false' do + expect { migrate! } + .to change { settings.where(job_token_scope_enabled: false).count } + .from(1).to(2) + end +end diff --git a/spec/migrations/schedule_backfill_draft_status_on_merge_requests_spec.rb b/spec/migrations/schedule_backfill_draft_status_on_merge_requests_spec.rb new file mode 100644 index 00000000000..5a1c07d810f --- /dev/null +++ b/spec/migrations/schedule_backfill_draft_status_on_merge_requests_spec.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require 'spec_helper' + +require_migration! + +RSpec.describe ScheduleBackfillDraftStatusOnMergeRequests, :sidekiq do + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + let(:merge_requests) { table(:merge_requests) } + + let(:group) { namespaces.create!(name: 'gitlab', path: 'gitlab') } + let(:project) { projects.create!(namespace_id: group.id) } + + let(:draft_prefixes) { ["[Draft]", "(Draft)", "Draft:", "Draft", "[WIP]", "WIP:", "WIP"] } + + def create_merge_request(params) + common_params = { + target_project_id: project.id, + target_branch: 'feature1', + source_branch: 'master' + } + + merge_requests.create!(common_params.merge(params)) + end + + before do + draft_prefixes.each do |prefix| + (1..4).each do |n| + create_merge_request( + title: "#{prefix} This is a title", + draft: false, + state_id: n + ) + end + end + + stub_const("#{described_class}::BATCH_SIZE", 1) + end + + it 'schedules BackfillDraftStatusOnMergeRequests background jobs' do + Sidekiq::Testing.fake! do + draft_mrs = Gitlab::BackgroundMigration::BackfillDraftStatusOnMergeRequests::MergeRequest.eligible + + first_mr_id = draft_mrs.first.id + second_mr_id = draft_mrs.second.id + + freeze_time do + migrate! + + expect(BackgroundMigrationWorker.jobs.size).to eq(7) + expect(described_class::MIGRATION) + .to be_scheduled_delayed_migration(2.minutes, first_mr_id, first_mr_id) + expect(described_class::MIGRATION) + .to be_scheduled_delayed_migration(4.minutes, second_mr_id, second_mr_id) + end + end + end +end diff --git a/spec/migrations/schedule_delete_orphaned_deployments_spec.rb b/spec/migrations/schedule_delete_orphaned_deployments_spec.rb new file mode 100644 index 00000000000..618958a3d90 --- /dev/null +++ b/spec/migrations/schedule_delete_orphaned_deployments_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'spec_helper' + +require_migration! + +RSpec.describe ScheduleDeleteOrphanedDeployments, :sidekiq, schema: 20210617161348 do + let!(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') } + let!(:project) { table(:projects).create!(namespace_id: namespace.id) } + let!(:environment) { table(:environments).create!(name: 'production', slug: 'production', project_id: project.id) } + let(:background_migration_jobs) { table(:background_migration_jobs) } + + before do + create_deployment!(environment.id, project.id) + create_deployment!(environment.id, project.id) + create_deployment!(environment.id, project.id) + create_deployment!(non_existing_record_id, project.id) + create_deployment!(non_existing_record_id, project.id) + create_deployment!(non_existing_record_id, project.id) + create_deployment!(non_existing_record_id, project.id) + + stub_const("#{described_class}::BATCH_SIZE", 1) + end + + it 'schedules DeleteOrphanedDeployments background jobs' do + Sidekiq::Testing.fake! do + freeze_time do + migrate! + + expect(BackgroundMigrationWorker.jobs.size).to eq(7) + table(:deployments).find_each do |deployment| + expect(described_class::MIGRATION).to be_scheduled_migration(deployment.id, deployment.id) + end + end + end + end + + def create_deployment!(environment_id, project_id) + table(:deployments).create!( + environment_id: environment_id, + project_id: project_id, + ref: 'master', + tag: false, + sha: 'x', + status: 1, + iid: table(:deployments).count + 1) + end +end |