diff options
Diffstat (limited to 'spec/migrations')
27 files changed, 1993 insertions, 2 deletions
diff --git a/spec/migrations/20221219122320_copy_clickhouse_connection_string_to_encrypted_var_spec.rb b/spec/migrations/20221219122320_copy_clickhouse_connection_string_to_encrypted_var_spec.rb index 7ff033ab0c2..48702e866e0 100644 --- a/spec/migrations/20221219122320_copy_clickhouse_connection_string_to_encrypted_var_spec.rb +++ b/spec/migrations/20221219122320_copy_clickhouse_connection_string_to_encrypted_var_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' require_migration! -RSpec.describe CopyClickhouseConnectionStringToEncryptedVar, feature_category: :product_analytics do +RSpec.describe CopyClickhouseConnectionStringToEncryptedVar, feature_category: :product_analytics_data_management do let!(:migration) { described_class.new } let(:setting) { table(:application_settings).create!(clickhouse_connection_string: 'https://example.com/test') } diff --git a/spec/migrations/20230327123333_backfill_product_analytics_data_collector_host_spec.rb b/spec/migrations/20230327123333_backfill_product_analytics_data_collector_host_spec.rb index 253512c9194..05cc065e6c3 100644 --- a/spec/migrations/20230327123333_backfill_product_analytics_data_collector_host_spec.rb +++ b/spec/migrations/20230327123333_backfill_product_analytics_data_collector_host_spec.rb @@ -3,7 +3,7 @@ require "spec_helper" require_migration! -RSpec.describe BackfillProductAnalyticsDataCollectorHost, feature_category: :product_analytics do +RSpec.describe BackfillProductAnalyticsDataCollectorHost, feature_category: :product_analytics_data_management do let!(:application_settings) { table(:application_settings) } describe '#up' do diff --git a/spec/migrations/20230612232000_queue_backfill_dismissal_reason_in_vulnerability_reads_spec.rb b/spec/migrations/20230612232000_queue_backfill_dismissal_reason_in_vulnerability_reads_spec.rb new file mode 100644 index 00000000000..56bfa50abd0 --- /dev/null +++ b/spec/migrations/20230612232000_queue_backfill_dismissal_reason_in_vulnerability_reads_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe QueueBackfillDismissalReasonInVulnerabilityReads, feature_category: :vulnerability_management do + let!(:batched_migration) { described_class::MIGRATION } + + it 'schedules a new batched migration' do + reversible_migration do |migration| + migration.before -> { + expect(batched_migration).not_to have_scheduled_batched_migration + } + + migration.after -> { + expect(batched_migration).to have_scheduled_batched_migration( + table_name: :vulnerability_reads, + column_name: :id, + interval: described_class::DELAY_INTERVAL, + batch_size: described_class::BATCH_SIZE, + sub_batch_size: described_class::SUB_BATCH_SIZE + ) + } + end + end +end diff --git a/spec/migrations/20230712145557_queue_backfill_missing_vulnerability_dismissal_details_spec.rb b/spec/migrations/20230712145557_queue_backfill_missing_vulnerability_dismissal_details_spec.rb new file mode 100644 index 00000000000..6595164da41 --- /dev/null +++ b/spec/migrations/20230712145557_queue_backfill_missing_vulnerability_dismissal_details_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe QueueBackfillMissingVulnerabilityDismissalDetails, feature_category: :vulnerability_management do + let!(:batched_migration) { described_class::MIGRATION } + + it 'schedules a new batched migration' do + reversible_migration do |migration| + migration.before -> { + expect(batched_migration).not_to have_scheduled_batched_migration + } + + migration.after -> { + expect(batched_migration).to have_scheduled_batched_migration( + table_name: :vulnerabilities, + column_name: :id, + interval: described_class::DELAY_INTERVAL, + batch_size: described_class::BATCH_SIZE, + sub_batch_size: described_class::SUB_BATCH_SIZE + ) + } + end + end +end diff --git a/spec/migrations/20230714015909_add_index_for_member_expiring_query_spec.rb b/spec/migrations/20230714015909_add_index_for_member_expiring_query_spec.rb new file mode 100644 index 00000000000..524354ecc9a --- /dev/null +++ b/spec/migrations/20230714015909_add_index_for_member_expiring_query_spec.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe AddIndexForMemberExpiringQuery, :migration, feature_category: :groups_and_projects do + let(:index_name) { 'index_members_on_expiring_at_access_level_id' } + + it 'correctly migrates up and down' do + expect(subject).not_to be_index_exists_by_name(:members, index_name) + + migrate! + + expect(subject).to be_index_exists_by_name(:members, index_name) + end +end diff --git a/spec/migrations/20230719083202_backfill_project_statistics_storage_size_without_pipeline_artifacts_size_spec.rb b/spec/migrations/20230719083202_backfill_project_statistics_storage_size_without_pipeline_artifacts_size_spec.rb new file mode 100644 index 00000000000..c3183a5da1b --- /dev/null +++ b/spec/migrations/20230719083202_backfill_project_statistics_storage_size_without_pipeline_artifacts_size_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe BackfillProjectStatisticsStorageSizeWithoutPipelineArtifactsSize, feature_category: :consumables_cost_management do + let!(:batched_migration) { described_class::MIGRATION } + + it 'does not schedule background jobs when Gitlab.org_or_com? is false' do + allow(Gitlab).to receive(:dev_or_test_env?).and_return(false) + allow(Gitlab).to receive(:org_or_com?).and_return(false) + + reversible_migration do |migration| + migration.before -> { + expect(batched_migration).not_to have_scheduled_batched_migration + } + + migration.after -> { + expect(batched_migration).not_to have_scheduled_batched_migration + } + end + end + + it 'schedules a new batched migration' do + allow(Gitlab).to receive(:dev_or_test_env?).and_return(false) + allow(Gitlab).to receive(:org_or_com?).and_return(true) + + reversible_migration do |migration| + migration.before -> { + expect(batched_migration).not_to have_scheduled_batched_migration + } + + migration.after -> { + expect(batched_migration).to have_scheduled_batched_migration( + table_name: :project_statistics, + column_name: :project_id, + interval: described_class::DELAY_INTERVAL, + batch_size: described_class::BATCH_SIZE, + sub_batch_size: described_class::SUB_BATCH_SIZE + ) + } + end + end +end diff --git a/spec/migrations/20230723203612_backfill_default_branch_protection_application_setting_spec.rb b/spec/migrations/20230723203612_backfill_default_branch_protection_application_setting_spec.rb new file mode 100644 index 00000000000..dcb65e22196 --- /dev/null +++ b/spec/migrations/20230723203612_backfill_default_branch_protection_application_setting_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe BackfillDefaultBranchProtectionApplicationSetting, :migration, feature_category: :database do + let(:application_settings_table) { table(:application_settings) } + + before do + 5.times do |branch_protection| + application_settings_table.create!(default_branch_protection: branch_protection, + default_branch_protection_defaults: {}) + end + end + + it 'schedules a new batched migration' do + reversible_migration do |migration| + migration.before -> { + 5.times do |branch_protection| + expect(migrated_attribute(branch_protection)).to eq({}) + end + } + + migration.after -> { + expect(migrated_attribute(0)).to eq({ "allow_force_push" => true, + "allowed_to_merge" => [{ "access_level" => 30 }], + "allowed_to_push" => [{ "access_level" => 30 }] }) + expect(migrated_attribute(1)).to eq({ "allow_force_push" => false, + "allowed_to_merge" => [{ "access_level" => 30 }], + "allowed_to_push" => [{ "access_level" => 30 }] }) + expect(migrated_attribute(2)).to eq({ "allow_force_push" => false, + "allowed_to_merge" => [{ "access_level" => 40 }], + "allowed_to_push" => [{ "access_level" => 40 }] }) + expect(migrated_attribute(3)).to eq({ "allow_force_push" => true, + "allowed_to_merge" => [{ "access_level" => 30 }], + "allowed_to_push" => [{ "access_level" => 40 }] }) + expect(migrated_attribute(4)).to eq({ "allow_force_push" => true, + "allowed_to_merge" => [{ "access_level" => 30 }], + "allowed_to_push" => [{ "access_level" => 40 }], + "developer_can_initial_push" => true }) + } + end + end + + def migrated_attribute(branch_protection) + application_settings_table + .where(default_branch_protection: branch_protection) + .last.default_branch_protection_defaults + end +end diff --git a/spec/migrations/20230724071541_queue_backfill_default_branch_protection_namespace_setting_spec.rb b/spec/migrations/20230724071541_queue_backfill_default_branch_protection_namespace_setting_spec.rb new file mode 100644 index 00000000000..5ba8c6b853c --- /dev/null +++ b/spec/migrations/20230724071541_queue_backfill_default_branch_protection_namespace_setting_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe QueueBackfillDefaultBranchProtectionNamespaceSetting, feature_category: :database do + let!(:batched_migration) { described_class::MIGRATION } + + it 'schedules a new batched migration' do + reversible_migration do |migration| + migration.before -> { + expect(batched_migration).not_to have_scheduled_batched_migration + } + + migration.after -> { + expect(batched_migration).to have_scheduled_batched_migration( + table_name: :namespace_settings, + column_name: :namespace_id, + interval: described_class::DELAY_INTERVAL, + batch_size: described_class::BATCH_SIZE, + sub_batch_size: described_class::SUB_BATCH_SIZE + ) + } + end + end +end diff --git a/spec/migrations/20230724164745_queue_delete_orphaned_transferred_project_approval_rules_spec.rb b/spec/migrations/20230724164745_queue_delete_orphaned_transferred_project_approval_rules_spec.rb new file mode 100644 index 00000000000..e4fa7216ae2 --- /dev/null +++ b/spec/migrations/20230724164745_queue_delete_orphaned_transferred_project_approval_rules_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe QueueDeleteOrphanedTransferredProjectApprovalRules, feature_category: :security_policy_management do + let!(:batched_migration) { described_class::MIGRATION } + + it 'schedules a new batched migration' do + reversible_migration do |migration| + migration.before -> { + expect(batched_migration).not_to have_scheduled_batched_migration + } + + migration.after -> { + expect(batched_migration).to have_scheduled_batched_migration( + table_name: :approval_project_rules, + column_name: :id, + interval: described_class::DELAY_INTERVAL, + batch_size: described_class::BATCH_SIZE, + sub_batch_size: described_class::SUB_BATCH_SIZE + ) + } + end + end +end diff --git a/spec/migrations/20230728174927_add_epic_work_item_type_spec.rb b/spec/migrations/20230728174927_add_epic_work_item_type_spec.rb new file mode 100644 index 00000000000..8f0227950e1 --- /dev/null +++ b/spec/migrations/20230728174927_add_epic_work_item_type_spec.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe AddEpicWorkItemType, :migration, feature_category: :team_planning do + include MigrationHelpers::WorkItemTypesHelper + + let(:work_item_types) { table(:work_item_types) } + let(:work_item_widget_definitions) { table(:work_item_widget_definitions) } + let(:work_item_hierarchy_restrictions) { table(:work_item_hierarchy_restrictions) } + let(:base_types) do + { + issue: 0, + incident: 1, + test_case: 2, + requirement: 3, + task: 4, + objective: 5, + key_result: 6, + epic: 7 + } + end + + after(:all) do + # Make sure base types are recreated after running the migration + # because migration specs are not run in a transaction + reset_work_item_types + end + + before do + reset_db_state_prior_to_migration + end + + it 'adds the epic type, widget definitions and hierarchy restrictions', :aggregate_failures do + expect do + migrate! + end.to change { work_item_types.count }.by(1) + .and(change { work_item_widget_definitions.count }.by(10)) + .and(change { work_item_hierarchy_restrictions.count }.by(2)) + + epic_type = work_item_types.last + issue_type = work_item_types.find_by!(namespace_id: nil, base_type: base_types[:issue]) + + expect(work_item_types.pluck(:base_type)).to include(base_types[:epic]) + expect( + work_item_widget_definitions.where(work_item_type_id: epic_type.id).pluck(:widget_type) + ).to match_array(described_class::EPIC_WIDGETS.values) + expect( + work_item_hierarchy_restrictions.where(parent_type_id: epic_type.id).pluck(:child_type_id, :maximum_depth) + ).to contain_exactly([epic_type.id, 9], [issue_type.id, 1]) + end + + it 'skips creating the new type an it\'s definitions' do + work_item_types.find_or_create_by!( + name: 'Epic', namespace_id: nil, base_type: base_types[:epic], icon_name: 'issue-type-epic' + ) + + expect do + migrate! + end.to not_change(work_item_types, :count) + .and(not_change(work_item_widget_definitions, :count)) + .and(not_change(work_item_hierarchy_restrictions, :count)) + end + + def reset_db_state_prior_to_migration + # Database needs to be in a similar state as when this migration was created + work_item_types.delete_all + work_item_types.find_or_create_by!( + name: 'Issue', namespace_id: nil, base_type: base_types[:issue], icon_name: 'issue-type-issue' + ) + work_item_types.find_or_create_by!( + name: 'Incident', namespace_id: nil, base_type: base_types[:incident], icon_name: 'issue-type-incident' + ) + work_item_types.find_or_create_by!( + name: 'Test Case', namespace_id: nil, base_type: base_types[:test_case], icon_name: 'issue-type-test-case' + ) + work_item_types.find_or_create_by!( + name: 'Requirement', namespace_id: nil, base_type: base_types[:requirement], icon_name: 'issue-type-requirements' + ) + work_item_types.find_or_create_by!( + name: 'Task', namespace_id: nil, base_type: base_types[:task], icon_name: 'issue-type-task' + ) + work_item_types.find_or_create_by!( + name: 'Objective', namespace_id: nil, base_type: base_types[:objective], icon_name: 'issue-type-objective' + ) + work_item_types.find_or_create_by!( + name: 'Key Result', namespace_id: nil, base_type: base_types[:key_result], icon_name: 'issue-type-keyresult' + ) + end +end diff --git a/spec/migrations/20230801150214_retry_cleanup_bigint_conversion_for_events_for_gitlab_com_spec.rb b/spec/migrations/20230801150214_retry_cleanup_bigint_conversion_for_events_for_gitlab_com_spec.rb new file mode 100644 index 00000000000..0eac9f28fcd --- /dev/null +++ b/spec/migrations/20230801150214_retry_cleanup_bigint_conversion_for_events_for_gitlab_com_spec.rb @@ -0,0 +1,147 @@ +# frozen_string_literal: true + +require 'spec_helper' + +require_migration! + +RSpec.describe RetryCleanupBigintConversionForEventsForGitlabCom, :migration, feature_category: :database do + let(:migration) { described_class.new } + let(:connection) { migration.connection } + let(:column_name) { 'target_id_convert_to_bigint' } + let(:events_table) { table(:events) } + + before do + allow(migration).to receive(:should_run?).and_return(should_run?) + end + + shared_examples 'skips the up migration' do + it "doesn't calls cleanup_conversion_of_integer_to_bigint method" do + disable_migrations_output do + expect(migration).not_to receive(:cleanup_conversion_of_integer_to_bigint) + + migration.up + end + end + end + + shared_examples 'skips the down migration' do + it "doesn't calls restore_conversion_of_integer_to_bigint method" do + disable_migrations_output do + expect(migration).not_to receive(:restore_conversion_of_integer_to_bigint) + + migration.down + end + end + end + + describe '#up' do + context 'when column still exists' do + before do + # Ensures the correct state of db before the test + connection.execute('ALTER TABLE events ADD COLUMN IF NOT EXISTS target_id_convert_to_bigint integer') + connection.execute('CREATE OR REPLACE FUNCTION trigger_cd1aeb22b34a() RETURNS trigger LANGUAGE plpgsql AS $$ + BEGIN NEW."target_id_convert_to_bigint" := NEW."target_id"; RETURN NEW; END; $$;') + connection.execute('DROP TRIGGER IF EXISTS trigger_cd1aeb22b34a ON events') + connection.execute('CREATE TRIGGER trigger_cd1aeb22b34a BEFORE INSERT OR UPDATE ON events FOR EACH ROW EXECUTE + FUNCTION trigger_cd1aeb22b34a()') + end + + context 'when is GitLab.com, dev, or test' do + let(:should_run?) { true } + + it 'drop the temporary columns' do + disable_migrations_output do + reversible_migration do |migration| + migration.before -> { + events_table.reset_column_information + expect(events_table.columns.find { |c| c.name == 'target_id_convert_to_bigint' }).not_to be_nil + } + + migration.after -> { + events_table.reset_column_information + expect(events_table.columns.find { |c| c.name == 'target_id_convert_to_bigint' }).to be_nil + } + end + end + end + end + + context 'when is a self-managed instance' do + let(:should_run?) { false } + + it_behaves_like 'skips the up migration' + end + end + + context 'when column not exists' do + before do + connection.execute('ALTER TABLE events DROP COLUMN IF EXISTS target_id_convert_to_bigint') + end + + context 'when is GitLab.com, dev, or test' do + let(:should_run?) { true } + + it_behaves_like 'skips the up migration' + end + + context 'when is a self-managed instance' do + let(:should_run?) { false } + + it_behaves_like 'skips the up migration' + end + end + end + + describe '#down' do + context 'when column still exists' do + before do + # Ensures the correct state of db before the test + connection.execute('ALTER TABLE events ADD COLUMN IF NOT EXISTS target_id_convert_to_bigint integer') + connection.execute('CREATE OR REPLACE FUNCTION trigger_cd1aeb22b34a() RETURNS trigger LANGUAGE plpgsql AS $$ + BEGIN NEW."target_id_convert_to_bigint" := NEW."target_id"; RETURN NEW; END; $$;') + connection.execute('DROP TRIGGER IF EXISTS trigger_cd1aeb22b34a ON events') + connection.execute('CREATE TRIGGER trigger_cd1aeb22b34a BEFORE INSERT OR UPDATE ON events FOR EACH ROW EXECUTE + FUNCTION trigger_cd1aeb22b34a()') + end + + context 'when is GitLab.com, dev, or test' do + let(:should_run?) { true } + + it_behaves_like 'skips the down migration' + end + + context 'when is a self-managed instance' do + let(:should_run?) { false } + + it_behaves_like 'skips the down migration' + end + end + + context 'when column not exists' do + before do + connection.execute('ALTER TABLE events DROP COLUMN IF EXISTS target_id_convert_to_bigint') + end + + context 'when is GitLab.com, dev, or test' do + let(:should_run?) { true } + + it 'restore the temporary columns' do + disable_migrations_output do + migration.down + + column = events_table.columns.find { |c| c.name == 'target_id_convert_to_bigint' } + + expect(column).not_to be_nil + expect(column.sql_type).to eq('integer') + end + end + end + + context 'when is a self-managed instance' do + let(:should_run?) { false } + + it_behaves_like 'skips the down migration' + end + end + end +end diff --git a/spec/migrations/20230802085923_queue_fix_allow_descendants_override_disabled_shared_runners_spec.rb b/spec/migrations/20230802085923_queue_fix_allow_descendants_override_disabled_shared_runners_spec.rb new file mode 100644 index 00000000000..c296ba24d9d --- /dev/null +++ b/spec/migrations/20230802085923_queue_fix_allow_descendants_override_disabled_shared_runners_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe QueueFixAllowDescendantsOverrideDisabledSharedRunners, feature_category: :runner_fleet do + let!(:batched_migration) { described_class::MIGRATION } + + it 'schedules a new batched migration' do + reversible_migration do |migration| + migration.before -> { + expect(batched_migration).not_to have_scheduled_batched_migration + } + + migration.after -> { + expect(batched_migration).to have_scheduled_batched_migration( + table_name: :namespaces, + column_name: :id, + interval: described_class::DELAY_INTERVAL, + batch_size: described_class::BATCH_SIZE, + sub_batch_size: described_class::SUB_BATCH_SIZE + ) + } + end + end +end diff --git a/spec/migrations/20230803125434_add_has_merge_request_on_vulnerability_reads_trigger_spec.rb b/spec/migrations/20230803125434_add_has_merge_request_on_vulnerability_reads_trigger_spec.rb new file mode 100644 index 00000000000..374a494fbec --- /dev/null +++ b/spec/migrations/20230803125434_add_has_merge_request_on_vulnerability_reads_trigger_spec.rb @@ -0,0 +1,178 @@ +# frozen_string_literal: true + +require 'spec_helper' + +require_migration! + +RSpec.describe AddHasMergeRequestOnVulnerabilityReadsTrigger, feature_category: :vulnerability_management do + let(:migration) { described_class.new } + let(:vulnerability_reads) { table(:vulnerability_reads) } + let(:merge_requests) { table(:merge_requests) } + let(:merge_request_links) { table(:vulnerability_merge_request_links) } + let(:vulnerabilities) { table(:vulnerabilities) } + let(:vulnerabilities_findings) { table(:vulnerability_occurrences) } + + let(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') } + let(:user) { table(:users).create!(id: 13, email: 'author@example.com', username: 'author', projects_limit: 10) } + let(:project) { table(:projects).create!(id: 123, namespace_id: namespace.id, project_namespace_id: namespace.id) } + + let(:scanner) do + table(:vulnerability_scanners).create!( + project_id: project.id, external_id: 'test 1', name: 'test scanner 1') + end + + let(:merge_request) do + create_merge_request!( + target_project_id: project.id, + source_branch: "other", + target_branch: "main", + author_id: user.id, + title: 'Feedback Merge Request 1' + ) + end + + let(:vulnerability) do + create_vulnerability!( + project_id: project.id, + author_id: user.id + ) + end + + let(:identifier) do + table(:vulnerability_identifiers).create!( + project_id: project.id, + external_type: 'uuid-v5', + external_id: 'uuid-v5', + fingerprint: '7e394d1b1eb461a7406d7b1e08f057a1cf11287a', + name: 'Identifier for UUIDv5') + end + + let(:vulnerability_read) { vulnerability_reads.first } + + before do + create_finding!( + vulnerability_id: vulnerability.id, + project_id: project.id, + scanner_id: scanner.id, + primary_identifier_id: identifier.id + ) + + vulnerability_read.reload + end + + describe '#up' do + before do + migrate! + end + + describe 'INSERT trigger' do + it 'updates has_merge_request in vulnerability_reads' do + expect do + merge_request_links.create!( + vulnerability_id: vulnerability.id, merge_request_id: merge_request.id) + end.to change { vulnerability_read.reload.has_merge_request }.from(false).to(true) + end + end + + describe 'DELETE trigger' do + let(:merge_request2) do + create_merge_request!( + target_project_id: project.id, + source_branch: "other_2", + target_branch: "main", + author_id: user.id, + title: 'Feedback Merge Request 2' + ) + end + + it 'does not change has_merge_request when there exists another merge_request' do + merge_request_link1 = merge_request_links.create!( + vulnerability_id: vulnerability.id, merge_request_id: merge_request.id) + + merge_request_links.create!( + vulnerability_id: vulnerability.id, merge_request_id: merge_request2.id) + + expect do + merge_request_link1.delete + end.not_to change { vulnerability_read.reload.has_merge_request } + end + + it 'unsets has_merge_request when all merge_requests are deleted' do + merge_request_link1 = merge_request_links.create!( + vulnerability_id: vulnerability.id, merge_request_id: merge_request.id) + + merge_request_link2 = merge_request_links.create!( + vulnerability_id: vulnerability.id, merge_request_id: merge_request2.id) + + expect do + merge_request_link1.delete + merge_request_link2.delete + end.to change { vulnerability_read.reload.has_merge_request }.from(true).to(false) + end + end + end + + describe '#down' do + before do + migration.up + migration.down + end + + it 'drops the trigger' do + expect do + merge_request_links.create!( + vulnerability_id: vulnerability.id, merge_request_id: merge_request.id) + end.not_to change { vulnerability_read.reload.has_merge_request } + end + end + + private + + def create_vulnerability!(project_id:, author_id:, title: 'test', severity: 7, confidence: 7, report_type: 0) + vulnerabilities.create!( + project_id: project_id, + author_id: author_id, + title: title, + severity: severity, + confidence: confidence, + report_type: report_type + ) + end + + def create_merge_request!(overrides = {}) + attrs = { + target_project_id: project.id, + source_branch: "other", + target_branch: "main", + author_id: user.id, + title: 'Feedback Merge Request' + }.merge(overrides) + + merge_requests.create!(attrs) + end + + # rubocop:disable Metrics/ParameterLists + def create_finding!( + vulnerability_id:, project_id:, scanner_id:, primary_identifier_id:, + name: "test", severity: 7, confidence: 7, report_type: 0, + project_fingerprint: '123qweasdzxc', location: { "image" => "alpine:3.4" }, location_fingerprint: 'test', + metadata_version: 'test', raw_metadata: 'test', uuid: SecureRandom.uuid) + vulnerabilities_findings.create!( + vulnerability_id: vulnerability_id, + project_id: project_id, + name: name, + severity: severity, + confidence: confidence, + report_type: report_type, + project_fingerprint: project_fingerprint, + scanner_id: scanner_id, + primary_identifier_id: primary_identifier_id, + location: location, + location_fingerprint: location_fingerprint, + metadata_version: metadata_version, + raw_metadata: raw_metadata, + uuid: uuid + ) + end + # rubocop:enable Metrics/ParameterLists +end diff --git a/spec/migrations/20230804053643_add_ticket_work_item_type_spec.rb b/spec/migrations/20230804053643_add_ticket_work_item_type_spec.rb new file mode 100644 index 00000000000..9a6eeb44254 --- /dev/null +++ b/spec/migrations/20230804053643_add_ticket_work_item_type_spec.rb @@ -0,0 +1,97 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe AddTicketWorkItemType, :migration, feature_category: :service_desk do + include MigrationHelpers::WorkItemTypesHelper + + let(:work_item_types) { table(:work_item_types) } + let(:work_item_widget_definitions) { table(:work_item_widget_definitions) } + let(:work_item_hierarchy_restrictions) { table(:work_item_hierarchy_restrictions) } + let(:base_types) do + { + issue: 0, + incident: 1, + test_case: 2, + requirement: 3, + task: 4, + objective: 5, + key_result: 6, + epic: 7, + ticket: 8 + } + end + + after(:all) do + # Make sure base types are recreated after running the migration + # because migration specs are not run in a transaction + reset_work_item_types + end + + before do + reset_db_state_prior_to_migration + end + + it 'adds the ticket type, widget definitions and hierarchy restrictions', :aggregate_failures do + expect do + migrate! + end.to change { work_item_types.count }.by(1) + .and(change { work_item_widget_definitions.count }.by(13)) + .and(change { work_item_hierarchy_restrictions.count }.by(2)) + + ticket_type = work_item_types.last + issue_type = work_item_types.find_by!(namespace_id: nil, base_type: base_types[:issue]) + + expect(work_item_types.pluck(:base_type)).to include(base_types[:ticket]) + expect( + work_item_widget_definitions.where(work_item_type_id: ticket_type.id).pluck(:widget_type) + ).to match_array(described_class::TICKET_WIDGETS.values) + expect( + work_item_hierarchy_restrictions.where(parent_type_id: ticket_type.id).pluck(:child_type_id, :maximum_depth) + ).to contain_exactly([ticket_type.id, 1], [issue_type.id, 1]) + end + + it "skips creating the new type and it's definitions when it already exists" do + work_item_types.find_or_create_by!( + name: 'Ticket', namespace_id: nil, base_type: base_types[:ticket], icon_name: 'issue-type-issue' + ) + + expect do + migrate! + end.to not_change(work_item_types, :count) + .and(not_change(work_item_widget_definitions, :count)) + .and(not_change(work_item_hierarchy_restrictions, :count)) + end + + it "skips creating the new type and it's definitions when type creation fails" do + allow(described_class::MigrationWorkItemType).to receive(:create) + .and_return(described_class::MigrationWorkItemType.new) + + expect do + migrate! + end.to not_change(work_item_types, :count) + .and(not_change(work_item_widget_definitions, :count)) + .and(not_change(work_item_hierarchy_restrictions, :count)) + end + + def reset_db_state_prior_to_migration + # Database needs to be in a similar state as when this migration was created + work_item_types.delete_all + + { + issue: { name: 'Issue', icon_name: 'issue-type-issue' }, + incident: { name: 'Incident', icon_name: 'issue-type-incident' }, + test_case: { name: 'Test Case', icon_name: 'issue-type-test-case' }, + requirement: { name: 'Requirement', icon_name: 'issue-type-requirements' }, + task: { name: 'Task', icon_name: 'issue-type-task' }, + objective: { name: 'Objective', icon_name: 'issue-type-objective' }, + key_result: { name: 'Key Result', icon_name: 'issue-type-keyresult' }, + epic: { name: 'Epic', icon_name: 'issue-type-epic' } + }.each do |type, opts| + work_item_types.find_or_create_by!( + name: opts[:name], namespace_id: nil, base_type: base_types[type], icon_name: opts[:icon_name] + ) + end + end +end diff --git a/spec/migrations/20230807083334_add_linked_items_work_item_widget_spec.rb b/spec/migrations/20230807083334_add_linked_items_work_item_widget_spec.rb new file mode 100644 index 00000000000..cd6da15403f --- /dev/null +++ b/spec/migrations/20230807083334_add_linked_items_work_item_widget_spec.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe AddLinkedItemsWorkItemWidget, :migration, feature_category: :portfolio_management do + it_behaves_like 'migration that adds widget to work items definitions', widget_name: 'Linked items' do + let(:work_item_type_count) { 8 } + end +end diff --git a/spec/migrations/20230809104753_swap_epic_user_mentions_note_id_to_bigint_for_self_hosts_spec.rb b/spec/migrations/20230809104753_swap_epic_user_mentions_note_id_to_bigint_for_self_hosts_spec.rb new file mode 100644 index 00000000000..7dcd40db88a --- /dev/null +++ b/spec/migrations/20230809104753_swap_epic_user_mentions_note_id_to_bigint_for_self_hosts_spec.rb @@ -0,0 +1,149 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe SwapEpicUserMentionsNoteIdToBigintForSelfHosts, feature_category: :database do + describe '#up' do + context 'when GitLab.com, dev, or test' do + before do + # As we call `schema_migrate_down!` before each example, and for this migration + # `#down` is same as `#up`, we need to ensure we start from the expected state. + connection = described_class.new.connection + connection.execute('ALTER TABLE epic_user_mentions ALTER COLUMN note_id TYPE bigint') + connection.execute('ALTER TABLE epic_user_mentions DROP COLUMN IF EXISTS note_id_convert_to_bigint') + end + + it 'does not swap the columns' do + # rubocop: disable RSpec/AnyInstanceOf + allow_any_instance_of(described_class).to receive(:com_or_dev_or_test_but_not_jh?).and_return(true) + # rubocop: enable RSpec/AnyInstanceOf + + epic_user_mentions = table(:epic_user_mentions) + + disable_migrations_output do + reversible_migration do |migration| + migration.before -> { + epic_user_mentions.reset_column_information + + expect(epic_user_mentions.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('bigint') + expect(epic_user_mentions.columns.find { |c| c.name == 'note_id_convert_to_bigint' }).to be nil + } + + migration.after -> { + epic_user_mentions.reset_column_information + + expect(epic_user_mentions.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('bigint') + expect(epic_user_mentions.columns.find { |c| c.name == 'note_id_convert_to_bigint' }).to be nil + } + end + end + end + end + + context 'when self-managed instance with the columns already swapped' do + before do + # As we call `schema_migrate_down!` before each example, and for this migration + # `#down` is same as `#up`, we need to ensure we start from the expected state. + connection = described_class.new.connection + connection.execute('ALTER TABLE epic_user_mentions ALTER COLUMN note_id TYPE bigint') + connection.execute('ALTER TABLE epic_user_mentions ADD COLUMN IF NOT EXISTS note_id_convert_to_bigint integer') + end + + it 'does not swap the columns' do + # rubocop: disable RSpec/AnyInstanceOf + allow_any_instance_of(described_class).to receive(:com_or_dev_or_test_but_not_jh?).and_return(false) + # rubocop: enable RSpec/AnyInstanceOf + + epic_user_mentions = table(:epic_user_mentions) + + migrate! + + expect(epic_user_mentions.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('bigint') + expect(epic_user_mentions.columns.find do |c| + c.name == 'note_id_convert_to_bigint' + end.sql_type).to eq('integer') + end + end + + context 'when self-managed instance with the `note_id_convert_to_bigint` column already dropped ' do + before do + # As we call `schema_migrate_down!` before each example, and for this migration + # `#down` is same as `#up`, we need to ensure we start from the expected state. + connection = described_class.new.connection + connection.execute('ALTER TABLE epic_user_mentions ALTER COLUMN note_id TYPE bigint') + connection.execute('ALTER TABLE epic_user_mentions DROP COLUMN IF EXISTS note_id_convert_to_bigint') + end + + it 'does not swap the columns' do + # rubocop: disable RSpec/AnyInstanceOf + allow_any_instance_of(described_class).to receive(:com_or_dev_or_test_but_not_jh?).and_return(false) + # rubocop: enable RSpec/AnyInstanceOf + + epic_user_mentions = table(:epic_user_mentions) + + disable_migrations_output do + reversible_migration do |migration| + migration.before -> { + epic_user_mentions.reset_column_information + + expect(epic_user_mentions.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('bigint') + expect(epic_user_mentions.columns.find { |c| c.name == 'note_id_convert_to_bigint' }).to be nil + } + + migration.after -> { + epic_user_mentions.reset_column_information + + expect(epic_user_mentions.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('bigint') + expect(epic_user_mentions.columns.find { |c| c.name == 'note_id_convert_to_bigint' }).to be nil + } + end + end + end + end + + context 'when self-managed instance' do + before do + # As we call `schema_migrate_down!` before each example, and for this migration + # `#down` is same as `#up`, we need to ensure we start from the expected state. + connection = described_class.new.connection + connection.execute('ALTER TABLE epic_user_mentions ALTER COLUMN note_id TYPE integer') + connection.execute('ALTER TABLE epic_user_mentions ADD COLUMN IF NOT EXISTS note_id_convert_to_bigint bigint') + connection.execute('ALTER TABLE epic_user_mentions ALTER COLUMN note_id_convert_to_bigint TYPE bigint') + connection.execute('DROP INDEX IF EXISTS index_epic_user_mentions_on_note_id_convert_to_bigint') + connection.execute('CREATE OR REPLACE FUNCTION trigger_c5a5f48f12b0() RETURNS trigger LANGUAGE plpgsql AS $$ + BEGIN NEW."note_id_convert_to_bigint" := NEW."note_id"; RETURN NEW; END; $$;') + end + + it 'swaps the columns' do + # rubocop: disable RSpec/AnyInstanceOf + allow_any_instance_of(described_class).to receive(:com_or_dev_or_test_but_not_jh?).and_return(false) + # rubocop: enable RSpec/AnyInstanceOf + + epic_user_mentions = table(:epic_user_mentions) + + disable_migrations_output do + reversible_migration do |migration| + migration.before -> { + epic_user_mentions.reset_column_information + + expect(epic_user_mentions.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('integer') + expect(epic_user_mentions.columns.find do |c| + c.name == 'note_id_convert_to_bigint' + end.sql_type).to eq('bigint') + } + + migration.after -> { + epic_user_mentions.reset_column_information + + expect(epic_user_mentions.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('bigint') + expect(epic_user_mentions.columns.find do |c| + c.name == 'note_id_convert_to_bigint' + end.sql_type).to eq('integer') + } + end + end + end + end + end +end diff --git a/spec/migrations/20230810103534_swap_suggestions_note_id_to_bigint_for_self_hosts_spec.rb b/spec/migrations/20230810103534_swap_suggestions_note_id_to_bigint_for_self_hosts_spec.rb new file mode 100644 index 00000000000..79583fb0a21 --- /dev/null +++ b/spec/migrations/20230810103534_swap_suggestions_note_id_to_bigint_for_self_hosts_spec.rb @@ -0,0 +1,149 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe SwapSuggestionsNoteIdToBigintForSelfHosts, feature_category: :database do + describe '#up' do + context 'when GitLab.com, dev, or test' do + before do + # As we call `schema_migrate_down!` before each example, and for this migration + # `#down` is same as `#up`, we need to ensure we start from the expected state. + connection = described_class.new.connection + connection.execute('ALTER TABLE suggestions ALTER COLUMN note_id TYPE bigint') + connection.execute('ALTER TABLE suggestions DROP COLUMN IF EXISTS note_id_convert_to_bigint') + end + + it 'does not swap the columns' do + # rubocop: disable RSpec/AnyInstanceOf + allow_any_instance_of(described_class).to receive(:com_or_dev_or_test_but_not_jh?).and_return(true) + # rubocop: enable RSpec/AnyInstanceOf + + suggestions = table(:suggestions) + + disable_migrations_output do + reversible_migration do |migration| + migration.before -> { + suggestions.reset_column_information + + expect(suggestions.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('bigint') + expect(suggestions.columns.find { |c| c.name == 'note_id_convert_to_bigint' }).to be nil + } + + migration.after -> { + suggestions.reset_column_information + + expect(suggestions.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('bigint') + expect(suggestions.columns.find { |c| c.name == 'note_id_convert_to_bigint' }).to be nil + } + end + end + end + end + + context 'when self-managed instance with the columns already swapped' do + before do + # As we call `schema_migrate_down!` before each example, and for this migration + # `#down` is same as `#up`, we need to ensure we start from the expected state. + connection = described_class.new.connection + connection.execute('ALTER TABLE suggestions ALTER COLUMN note_id TYPE bigint') + connection.execute('ALTER TABLE suggestions ADD COLUMN IF NOT EXISTS note_id_convert_to_bigint integer') + end + + it 'does not swap the columns' do + # rubocop: disable RSpec/AnyInstanceOf + allow_any_instance_of(described_class).to receive(:com_or_dev_or_test_but_not_jh?).and_return(false) + # rubocop: enable RSpec/AnyInstanceOf + + suggestions = table(:suggestions) + + migrate! + + expect(suggestions.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('bigint') + expect(suggestions.columns.find do |c| + c.name == 'note_id_convert_to_bigint' + end.sql_type).to eq('integer') + end + end + + context 'when self-managed instance with the `note_id_convert_to_bigint` column already dropped ' do + before do + # As we call `schema_migrate_down!` before each example, and for this migration + # `#down` is same as `#up`, we need to ensure we start from the expected state. + connection = described_class.new.connection + connection.execute('ALTER TABLE suggestions ALTER COLUMN note_id TYPE bigint') + connection.execute('ALTER TABLE suggestions DROP COLUMN IF EXISTS note_id_convert_to_bigint') + end + + it 'does not swap the columns' do + # rubocop: disable RSpec/AnyInstanceOf + allow_any_instance_of(described_class).to receive(:com_or_dev_or_test_but_not_jh?).and_return(false) + # rubocop: enable RSpec/AnyInstanceOf + + suggestions = table(:suggestions) + + disable_migrations_output do + reversible_migration do |migration| + migration.before -> { + suggestions.reset_column_information + + expect(suggestions.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('bigint') + expect(suggestions.columns.find { |c| c.name == 'note_id_convert_to_bigint' }).to be nil + } + + migration.after -> { + suggestions.reset_column_information + + expect(suggestions.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('bigint') + expect(suggestions.columns.find { |c| c.name == 'note_id_convert_to_bigint' }).to be nil + } + end + end + end + end + + context 'when self-managed instance' do + before do + # As we call `schema_migrate_down!` before each example, and for this migration + # `#down` is same as `#up`, we need to ensure we start from the expected state. + connection = described_class.new.connection + connection.execute('ALTER TABLE suggestions ALTER COLUMN note_id TYPE integer') + connection.execute('ALTER TABLE suggestions ADD COLUMN IF NOT EXISTS note_id_convert_to_bigint bigint') + connection.execute('ALTER TABLE suggestions ALTER COLUMN note_id_convert_to_bigint TYPE bigint') + connection.execute('DROP INDEX IF EXISTS index_suggestions_on_note_id_convert_to_bigint') + connection.execute('CREATE OR REPLACE FUNCTION trigger_ee7956d805e6() RETURNS trigger LANGUAGE plpgsql AS $$ + BEGIN NEW."note_id_convert_to_bigint" := NEW."note_id"; RETURN NEW; END; $$;') + end + + it 'swaps the columns' do + # rubocop: disable RSpec/AnyInstanceOf + allow_any_instance_of(described_class).to receive(:com_or_dev_or_test_but_not_jh?).and_return(false) + # rubocop: enable RSpec/AnyInstanceOf + + suggestions = table(:suggestions) + + disable_migrations_output do + reversible_migration do |migration| + migration.before -> { + suggestions.reset_column_information + + expect(suggestions.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('integer') + expect(suggestions.columns.find do |c| + c.name == 'note_id_convert_to_bigint' + end.sql_type).to eq('bigint') + } + + migration.after -> { + suggestions.reset_column_information + + expect(suggestions.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('bigint') + expect(suggestions.columns.find do |c| + c.name == 'note_id_convert_to_bigint' + end.sql_type).to eq('integer') + } + end + end + end + end + end +end diff --git a/spec/migrations/20230810123044_swap_snippet_user_mentions_note_id_to_bigint_for_self_hosts_spec.rb b/spec/migrations/20230810123044_swap_snippet_user_mentions_note_id_to_bigint_for_self_hosts_spec.rb new file mode 100644 index 00000000000..92b73901fec --- /dev/null +++ b/spec/migrations/20230810123044_swap_snippet_user_mentions_note_id_to_bigint_for_self_hosts_spec.rb @@ -0,0 +1,125 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe SwapSnippetUserMentionsNoteIdToBigintForSelfHosts, feature_category: :database do + let(:connection) { described_class.new.connection } + let(:snippet_user_mentions) { table(:snippet_user_mentions) } + + shared_examples 'column `note_id_convert_to_bigint` is already dropped' do + before do + connection.execute('ALTER TABLE snippet_user_mentions ALTER COLUMN note_id TYPE bigint') + connection.execute('ALTER TABLE snippet_user_mentions DROP COLUMN IF EXISTS note_id_convert_to_bigint') + end + + it 'does not swaps the columns' do + disable_migrations_output do + reversible_migration do |migration| + migration.before -> { + snippet_user_mentions.reset_column_information + + expect(snippet_user_mentions.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('bigint') + expect(snippet_user_mentions.columns.find { |c| c.name == 'note_id_convert_to_bigint' }).to be_nil + } + + migration.after -> { + snippet_user_mentions.reset_column_information + + expect(snippet_user_mentions.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('bigint') + expect(snippet_user_mentions.columns.find { |c| c.name == 'note_id_convert_to_bigint' }).to be_nil + } + end + end + end + end + + describe '#up' do + before do + # rubocop:disable RSpec/AnyInstanceOf + allow_any_instance_of(described_class).to( + receive(:com_or_dev_or_test_but_not_jh?).and_return(com_or_dev_or_test_but_not_jh?) + ) + # rubocop:enable RSpec/AnyInstanceOf + end + + context 'when GitLab.com, dev, or test' do + let(:com_or_dev_or_test_but_not_jh?) { true } + + it_behaves_like 'column `note_id_convert_to_bigint` is already dropped' + end + + context 'when self-managed instance with the `note_id_convert_to_bigint` column already dropped' do + let(:com_or_dev_or_test_but_not_jh?) { false } + + it_behaves_like 'column `note_id_convert_to_bigint` is already dropped' + end + + context 'when self-managed instance columns already swapped' do + let(:com_or_dev_or_test_but_not_jh?) { false } + + before do + connection.execute('ALTER TABLE snippet_user_mentions ALTER COLUMN note_id TYPE bigint') + connection.execute( + 'ALTER TABLE snippet_user_mentions ADD COLUMN IF NOT EXISTS note_id_convert_to_bigint integer' + ) + + disable_migrations_output { migrate! } + end + + after do + connection.execute('ALTER TABLE snippet_user_mentions DROP COLUMN IF EXISTS note_id_convert_to_bigint') + end + + it 'does not swaps the columns' do + expect(snippet_user_mentions.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('bigint') + expect(snippet_user_mentions.columns.find { |c| c.name == 'note_id_convert_to_bigint' }.sql_type).to( + eq('integer') + ) + end + end + + context 'when self-managed instance' do + let(:com_or_dev_or_test_but_not_jh?) { false } + + before do + connection.execute('ALTER TABLE snippet_user_mentions ALTER COLUMN note_id TYPE integer') + connection.execute( + 'ALTER TABLE snippet_user_mentions ADD COLUMN IF NOT EXISTS note_id_convert_to_bigint bigint' + ) + connection.execute('ALTER TABLE snippet_user_mentions ALTER COLUMN note_id_convert_to_bigint TYPE bigint') + connection.execute('DROP INDEX IF EXISTS index_snippet_user_mentions_on_note_id_convert_to_bigint CASCADE') + connection.execute('CREATE OR REPLACE FUNCTION trigger_bfc6e47be8cc() RETURNS trigger LANGUAGE plpgsql AS $$ + BEGIN NEW."note_id_convert_to_bigint" := NEW."note_id"; RETURN NEW; END; $$;') + end + + after do + connection.execute('ALTER TABLE snippet_user_mentions DROP COLUMN IF EXISTS note_id_convert_to_bigint') + end + + it 'swaps the columns' do + disable_migrations_output do + reversible_migration do |migration| + migration.before -> { + snippet_user_mentions.reset_column_information + + expect(snippet_user_mentions.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('integer') + expect(snippet_user_mentions.columns.find { |c| c.name == 'note_id_convert_to_bigint' }.sql_type).to( + eq('bigint') + ) + } + + migration.after -> { + snippet_user_mentions.reset_column_information + + expect(snippet_user_mentions.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('bigint') + expect(snippet_user_mentions.columns.find { |c| c.name == 'note_id_convert_to_bigint' }.sql_type).to( + eq('integer') + ) + } + end + end + end + end + end +end diff --git a/spec/migrations/20230811103941_swap_vulnerability_user_mentions_note_id_to_bigint_for_self_hosts_spec.rb b/spec/migrations/20230811103941_swap_vulnerability_user_mentions_note_id_to_bigint_for_self_hosts_spec.rb new file mode 100644 index 00000000000..c1aa2d4daec --- /dev/null +++ b/spec/migrations/20230811103941_swap_vulnerability_user_mentions_note_id_to_bigint_for_self_hosts_spec.rb @@ -0,0 +1,133 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe SwapVulnerabilityUserMentionsNoteIdToBigintForSelfHosts, feature_category: :database do + let(:connection) { described_class.new.connection } + let(:vulnerability_user_mentions) { table(:vulnerability_user_mentions) } + + shared_examples 'column `note_id_convert_to_bigint` is already dropped' do + before do + connection.execute('ALTER TABLE vulnerability_user_mentions ALTER COLUMN note_id TYPE bigint') + connection.execute('ALTER TABLE vulnerability_user_mentions DROP COLUMN IF EXISTS note_id_convert_to_bigint') + end + + it 'does not swaps the columns' do + disable_migrations_output do + reversible_migration do |migration| + migration.before -> { + vulnerability_user_mentions.reset_column_information + + expect(vulnerability_user_mentions.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('bigint') + expect(vulnerability_user_mentions.columns.find { |c| c.name == 'note_id_convert_to_bigint' }).to be_nil + } + + migration.after -> { + vulnerability_user_mentions.reset_column_information + + expect(vulnerability_user_mentions.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('bigint') + expect(vulnerability_user_mentions.columns.find { |c| c.name == 'note_id_convert_to_bigint' }).to be_nil + } + end + end + end + end + + describe '#up' do + before do + # rubocop:disable RSpec/AnyInstanceOf + allow_any_instance_of(described_class).to( + receive(:com_or_dev_or_test_but_not_jh?).and_return(com_or_dev_or_test_but_not_jh?) + ) + # rubocop:enable RSpec/AnyInstanceOf + end + + context 'when GitLab.com, dev, or test' do + let(:com_or_dev_or_test_but_not_jh?) { true } + + it_behaves_like 'column `note_id_convert_to_bigint` is already dropped' + end + + context 'when self-managed instance with the `note_id_convert_to_bigint` column already dropped' do + let(:com_or_dev_or_test_but_not_jh?) { false } + + it_behaves_like 'column `note_id_convert_to_bigint` is already dropped' + end + + context 'when self-managed instance columns already swapped' do + let(:com_or_dev_or_test_but_not_jh?) { false } + + before do + connection.execute('ALTER TABLE vulnerability_user_mentions ALTER COLUMN note_id TYPE bigint') + connection.execute( + 'ALTER TABLE vulnerability_user_mentions ADD COLUMN IF NOT EXISTS note_id_convert_to_bigint integer' + ) + + disable_migrations_output { migrate! } + end + + after do + connection.execute('ALTER TABLE vulnerability_user_mentions DROP COLUMN IF EXISTS note_id_convert_to_bigint') + end + + it 'does not swaps the columns' do + expect(vulnerability_user_mentions.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('bigint') + expect(vulnerability_user_mentions.columns.find { |c| c.name == 'note_id_convert_to_bigint' }.sql_type).to( + eq('integer') + ) + end + end + + context 'when self-managed instance' do + let(:com_or_dev_or_test_but_not_jh?) { false } + + before do + connection.execute('ALTER TABLE vulnerability_user_mentions ALTER COLUMN note_id TYPE integer') + connection.execute( + 'ALTER TABLE vulnerability_user_mentions ADD COLUMN IF NOT EXISTS note_id_convert_to_bigint bigint' + ) + connection.execute('ALTER TABLE vulnerability_user_mentions ALTER COLUMN note_id_convert_to_bigint TYPE bigint') + connection.execute( + 'DROP INDEX IF EXISTS index_vulnerability_user_mentions_on_note_id_convert_to_bigint CASCADE' + ) + connection.execute('CREATE OR REPLACE FUNCTION trigger_0e214b8a14f2() RETURNS trigger LANGUAGE plpgsql AS $$ + BEGIN NEW."note_id_convert_to_bigint" := NEW."note_id"; RETURN NEW; END; $$;') + end + + after do + connection.execute('ALTER TABLE vulnerability_user_mentions DROP COLUMN IF EXISTS note_id_convert_to_bigint') + end + + it 'swaps the columns' do + disable_migrations_output do + reversible_migration do |migration| + migration.before -> { + vulnerability_user_mentions.reset_column_information + + expect(vulnerability_user_mentions.columns.find do |c| + c.name == 'note_id' + end.sql_type).to eq('integer') + expect(vulnerability_user_mentions.columns.find do |c| + c.name == 'note_id_convert_to_bigint' + end.sql_type).to( + eq('bigint') + ) + } + + migration.after -> { + vulnerability_user_mentions.reset_column_information + + expect(vulnerability_user_mentions.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('bigint') + expect(vulnerability_user_mentions.columns.find do |c| + c.name == 'note_id_convert_to_bigint' + end.sql_type).to( + eq('integer') + ) + } + end + end + end + end + end +end diff --git a/spec/migrations/20230814144045_swap_timelogs_note_id_to_bigint_for_self_hosts_spec.rb b/spec/migrations/20230814144045_swap_timelogs_note_id_to_bigint_for_self_hosts_spec.rb new file mode 100644 index 00000000000..41b9f0bf4e9 --- /dev/null +++ b/spec/migrations/20230814144045_swap_timelogs_note_id_to_bigint_for_self_hosts_spec.rb @@ -0,0 +1,125 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe SwapTimelogsNoteIdToBigintForSelfHosts, feature_category: :database do + let(:connection) { described_class.new.connection } + let(:timelogs) { table(:timelogs) } + + shared_examples 'column `note_id_convert_to_bigint` is already dropped' do + before do + connection.execute('ALTER TABLE timelogs ALTER COLUMN note_id TYPE bigint') + connection.execute('ALTER TABLE timelogs DROP COLUMN IF EXISTS note_id_convert_to_bigint') + end + + it 'does not swaps the columns' do + disable_migrations_output do + reversible_migration do |migration| + migration.before -> { + timelogs.reset_column_information + + expect(timelogs.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('bigint') + expect(timelogs.columns.find { |c| c.name == 'note_id_convert_to_bigint' }).to be_nil + } + + migration.after -> { + timelogs.reset_column_information + + expect(timelogs.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('bigint') + expect(timelogs.columns.find { |c| c.name == 'note_id_convert_to_bigint' }).to be_nil + } + end + end + end + end + + describe '#up' do + before do + # rubocop:disable RSpec/AnyInstanceOf + allow_any_instance_of(described_class).to( + receive(:com_or_dev_or_test_but_not_jh?).and_return(com_or_dev_or_test_but_not_jh?) + ) + # rubocop:enable RSpec/AnyInstanceOf + end + + context 'when GitLab.com, dev, or test' do + let(:com_or_dev_or_test_but_not_jh?) { true } + + it_behaves_like 'column `note_id_convert_to_bigint` is already dropped' + end + + context 'when self-managed instance with the `note_id_convert_to_bigint` column already dropped' do + let(:com_or_dev_or_test_but_not_jh?) { false } + + it_behaves_like 'column `note_id_convert_to_bigint` is already dropped' + end + + context 'when self-managed instance columns already swapped' do + let(:com_or_dev_or_test_but_not_jh?) { false } + + before do + connection.execute('ALTER TABLE timelogs ALTER COLUMN note_id TYPE bigint') + connection.execute( + 'ALTER TABLE timelogs ADD COLUMN IF NOT EXISTS note_id_convert_to_bigint integer' + ) + + disable_migrations_output { migrate! } + end + + after do + connection.execute('ALTER TABLE timelogs DROP COLUMN IF EXISTS note_id_convert_to_bigint') + end + + it 'does not swaps the columns' do + expect(timelogs.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('bigint') + expect(timelogs.columns.find { |c| c.name == 'note_id_convert_to_bigint' }.sql_type).to( + eq('integer') + ) + end + end + + context 'when self-managed instance' do + let(:com_or_dev_or_test_but_not_jh?) { false } + + before do + connection.execute('ALTER TABLE timelogs ALTER COLUMN note_id TYPE integer') + connection.execute( + 'ALTER TABLE timelogs ADD COLUMN IF NOT EXISTS note_id_convert_to_bigint bigint' + ) + connection.execute('ALTER TABLE timelogs ALTER COLUMN note_id_convert_to_bigint TYPE bigint') + connection.execute('DROP INDEX IF EXISTS index_timelogs_on_note_id_convert_to_bigint CASCADE') + connection.execute('CREATE OR REPLACE FUNCTION trigger_428d92773fe7() RETURNS trigger LANGUAGE plpgsql AS $$ + BEGIN NEW."note_id_convert_to_bigint" := NEW."note_id"; RETURN NEW; END; $$;') + end + + after do + connection.execute('ALTER TABLE timelogs DROP COLUMN IF EXISTS note_id_convert_to_bigint') + end + + it 'swaps the columns' do + disable_migrations_output do + reversible_migration do |migration| + migration.before -> { + timelogs.reset_column_information + + expect(timelogs.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('integer') + expect(timelogs.columns.find { |c| c.name == 'note_id_convert_to_bigint' }.sql_type).to( + eq('bigint') + ) + } + + migration.after -> { + timelogs.reset_column_information + + expect(timelogs.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('bigint') + expect(timelogs.columns.find { |c| c.name == 'note_id_convert_to_bigint' }.sql_type).to( + eq('integer') + ) + } + end + end + end + end + end +end diff --git a/spec/migrations/add_expiry_notified_at_to_member_spec.rb b/spec/migrations/add_expiry_notified_at_to_member_spec.rb new file mode 100644 index 00000000000..30eaf06529e --- /dev/null +++ b/spec/migrations/add_expiry_notified_at_to_member_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe AddExpiryNotifiedAtToMember, feature_category: :system_access do + let(:members) { table(:members) } + + it 'correctly migrates up and down' do + reversible_migration do |migration| + migration.before -> { + expect(members.column_names).not_to include('expiry_notified_at') + } + + migration.after -> { + members.reset_column_information + expect(members.column_names).to include('expiry_notified_at') + } + end + end +end diff --git a/spec/migrations/cleanup_conversion_big_int_ci_build_needs_self_managed_spec.rb b/spec/migrations/cleanup_conversion_big_int_ci_build_needs_self_managed_spec.rb new file mode 100644 index 00000000000..03a8356c721 --- /dev/null +++ b/spec/migrations/cleanup_conversion_big_int_ci_build_needs_self_managed_spec.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe CleanupConversionBigIntCiBuildNeedsSelfManaged, feature_category: :database do + after do + connection = described_class.new.connection + connection.execute('ALTER TABLE ci_build_needs DROP COLUMN IF EXISTS id_convert_to_bigint') + end + + describe '#up' do + context 'when it is GitLab.com, dev, or test but not JiHu' do + before do + # As we call `schema_migrate_down!` before each example, and for this migration + # `#down` is same as `#up`, we need to ensure we start from the expected state. + connection = described_class.new.connection + connection.execute('ALTER TABLE ci_build_needs DROP COLUMN IF EXISTS id_convert_to_bigint') + end + + it 'does nothing' do + # rubocop: disable RSpec/AnyInstanceOf + allow_any_instance_of(described_class).to receive(:com_or_dev_or_test_but_not_jh?).and_return(true) + # rubocop: enable RSpec/AnyInstanceOf + + ci_build_needs = table(:ci_build_needs) + + disable_migrations_output do + reversible_migration do |migration| + migration.before -> { + ci_build_needs.reset_column_information + + expect(ci_build_needs.columns.find { |c| c.name == 'id_convert_to_bigint' }).to be nil + } + + migration.after -> { + ci_build_needs.reset_column_information + + expect(ci_build_needs.columns.find { |c| c.name == 'id_convert_to_bigint' }).to be nil + } + end + end + end + end + + context 'when there is a self-managed instance with the temporary column already dropped' do + before do + # As we call `schema_migrate_down!` before each example, and for this migration + # `#down` is same as `#up`, we need to ensure we start from the expected state. + connection = described_class.new.connection + connection.execute('ALTER TABLE ci_build_needs ALTER COLUMN id TYPE bigint') + connection.execute('ALTER TABLE ci_build_needs DROP COLUMN IF EXISTS id_convert_to_bigint') + end + + it 'does nothing' do + # rubocop: disable RSpec/AnyInstanceOf + allow_any_instance_of(described_class).to receive(:com_or_dev_or_test_but_not_jh?).and_return(false) + # rubocop: enable RSpec/AnyInstanceOf + + ci_build_needs = table(:ci_build_needs) + + migrate! + + expect(ci_build_needs.columns.find { |c| c.name == 'id' }.sql_type).to eq('bigint') + expect(ci_build_needs.columns.find { |c| c.name == 'id_convert_to_bigint' }).to be nil + end + end + + context 'when there is a self-managed instance with the temporary columns' do + before do + # As we call `schema_migrate_down!` before each example, and for this migration + # `#down` is same as `#up`, we need to ensure we start from the expected state. + connection = described_class.new.connection + connection.execute('ALTER TABLE ci_build_needs ALTER COLUMN id TYPE bigint') + connection.execute('ALTER TABLE ci_build_needs ADD COLUMN IF NOT EXISTS id_convert_to_bigint integer') + end + + it 'drops the temporary column' do + # rubocop: disable RSpec/AnyInstanceOf + allow_any_instance_of(described_class).to receive(:com_or_dev_or_test_but_not_jh?).and_return(false) + # rubocop: enable RSpec/AnyInstanceOf + + ci_build_needs = table(:ci_build_needs) + + disable_migrations_output do + reversible_migration do |migration| + migration.before -> { + ci_build_needs.reset_column_information + + expect(ci_build_needs.columns.find { |c| c.name == 'id' }.sql_type).to eq('bigint') + expect(ci_build_needs.columns.find do |c| + c.name == 'id_convert_to_bigint' + end.sql_type).to eq('integer') + } + + migration.after -> { + ci_build_needs.reset_column_information + + expect(ci_build_needs.columns.find { |c| c.name == 'id' }.sql_type).to eq('bigint') + expect(ci_build_needs.columns.find { |c| c.name == 'id_convert_to_bigint' }).to be nil + } + end + end + end + end + end +end diff --git a/spec/migrations/ensure_commit_user_mentions_note_id_bigint_backfill_is_finished_for_self_managed_spec.rb b/spec/migrations/ensure_commit_user_mentions_note_id_bigint_backfill_is_finished_for_self_managed_spec.rb new file mode 100644 index 00000000000..b281403204b --- /dev/null +++ b/spec/migrations/ensure_commit_user_mentions_note_id_bigint_backfill_is_finished_for_self_managed_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe EnsureCommitUserMentionsNoteIdBigintBackfillIsFinishedForSelfManaged, feature_category: :database do + describe '#up' do + let(:migration_arguments) do + { + job_class_name: 'CopyColumnUsingBackgroundMigrationJob', + table_name: 'commit_user_mentions', + column_name: 'id', + job_arguments: [['note_id'], ['note_id_convert_to_bigint']] + } + end + + it 'ensures the migration is completed for self-managed instances' do + expect_next_instance_of(described_class) do |instance| + expect(instance).to receive(:com_or_dev_or_test_but_not_jh?).and_return(false) + expect(instance).to receive(:ensure_batched_background_migration_is_finished).with(migration_arguments) + end + + migrate! + end + + it 'skips the check for GitLab.com, dev, or test' do + expect_next_instance_of(described_class) do |instance| + expect(instance).to receive(:com_or_dev_or_test_but_not_jh?).and_return(true) + expect(instance).not_to receive(:ensure_batched_background_migration_is_finished) + end + + migrate! + end + end +end diff --git a/spec/migrations/ensure_todos_bigint_backfill_completed_for_self_managed_spec.rb b/spec/migrations/ensure_todos_bigint_backfill_completed_for_self_managed_spec.rb new file mode 100644 index 00000000000..5187441377e --- /dev/null +++ b/spec/migrations/ensure_todos_bigint_backfill_completed_for_self_managed_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe EnsureTodosBigintBackfillCompletedForSelfManaged, feature_category: :database do + describe '#up' do + let(:migration_arguments) do + { + job_class_name: 'CopyColumnUsingBackgroundMigrationJob', + table_name: 'todos', + column_name: 'id', + job_arguments: [['note_id'], ['note_id_convert_to_bigint']] + } + end + + it 'ensures the migration is completed for self-managed instances' do + expect_next_instance_of(described_class) do |instance| + expect(instance).to receive(:com_or_dev_or_test_but_not_jh?).and_return(false) + expect(instance).to receive(:ensure_batched_background_migration_is_finished).with(migration_arguments) + end + + migrate! + end + + it 'skips the check for GitLab.com, dev, or test' do + expect_next_instance_of(described_class) do |instance| + expect(instance).to receive(:com_or_dev_or_test_but_not_jh?).and_return(true) + expect(instance).not_to receive(:ensure_batched_background_migration_is_finished) + end + + migrate! + end + end +end diff --git a/spec/migrations/swap_commit_user_mentions_note_id_to_bigint_for_self_managed_spec.rb b/spec/migrations/swap_commit_user_mentions_note_id_to_bigint_for_self_managed_spec.rb new file mode 100644 index 00000000000..b35da2f78db --- /dev/null +++ b/spec/migrations/swap_commit_user_mentions_note_id_to_bigint_for_self_managed_spec.rb @@ -0,0 +1,123 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe SwapCommitUserMentionsNoteIdToBigintForSelfManaged, feature_category: :database do + let(:connection) { described_class.new.connection } + let(:commit_user_mentions) { table(:commit_user_mentions) } + + shared_examples 'column `note_id_convert_to_bigint` is already dropped' do + before do + connection.execute('ALTER TABLE commit_user_mentions ALTER COLUMN note_id TYPE bigint') + connection.execute('ALTER TABLE commit_user_mentions DROP COLUMN IF EXISTS note_id_convert_to_bigint') + end + + it 'does not swaps the columns' do + disable_migrations_output do + reversible_migration do |migration| + migration.before -> { + commit_user_mentions.reset_column_information + + expect(commit_user_mentions.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('bigint') + expect(commit_user_mentions.columns.find { |c| c.name == 'note_id_convert_to_bigint' }).to be_nil + } + + migration.after -> { + commit_user_mentions.reset_column_information + + expect(commit_user_mentions.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('bigint') + expect(commit_user_mentions.columns.find { |c| c.name == 'note_id_convert_to_bigint' }).to be_nil + } + end + end + end + end + + describe '#up' do + before do + # rubocop:disable RSpec/AnyInstanceOf + allow_any_instance_of(described_class).to( + receive(:com_or_dev_or_test_but_not_jh?).and_return(com_or_dev_or_test_but_not_jh?) + ) + # rubocop:enable RSpec/AnyInstanceOf + end + + context 'when GitLab.com, dev, or test' do + let(:com_or_dev_or_test_but_not_jh?) { true } + + it_behaves_like 'column `note_id_convert_to_bigint` is already dropped' + end + + context 'when self-managed instance with the `note_id_convert_to_bigint` column already dropped' do + let(:com_or_dev_or_test_but_not_jh?) { false } + + it_behaves_like 'column `note_id_convert_to_bigint` is already dropped' + end + + context 'when self-managed instance columns already swapped' do + let(:com_or_dev_or_test_but_not_jh?) { false } + + before do + connection.execute('ALTER TABLE commit_user_mentions ALTER COLUMN note_id TYPE bigint') + connection.execute( + 'ALTER TABLE commit_user_mentions ADD COLUMN IF NOT EXISTS note_id_convert_to_bigint integer' + ) + + disable_migrations_output { migrate! } + end + + after do + connection.execute('ALTER TABLE commit_user_mentions DROP COLUMN IF EXISTS note_id_convert_to_bigint') + end + + it 'does not swaps the columns' do + expect(commit_user_mentions.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('bigint') + expect(commit_user_mentions.columns.find { |c| c.name == 'note_id_convert_to_bigint' }.sql_type).to( + eq('integer') + ) + end + end + + context 'when self-managed instance' do + let(:com_or_dev_or_test_but_not_jh?) { false } + + before do + connection.execute('ALTER TABLE commit_user_mentions ALTER COLUMN note_id TYPE integer') + connection.execute('ALTER TABLE commit_user_mentions ADD COLUMN IF NOT EXISTS note_id_convert_to_bigint bigint') + connection.execute('ALTER TABLE commit_user_mentions ALTER COLUMN note_id_convert_to_bigint TYPE bigint') + connection.execute('DROP INDEX IF EXISTS index_commit_user_mentions_on_note_id_convert_to_bigint CASCADE') + connection.execute('CREATE OR REPLACE FUNCTION trigger_17c3a95ee58a() RETURNS trigger LANGUAGE plpgsql AS $$ + BEGIN NEW."note_id_convert_to_bigint" := NEW."note_id"; RETURN NEW; END; $$;') + end + + after do + connection.execute('ALTER TABLE commit_user_mentions DROP COLUMN IF EXISTS note_id_convert_to_bigint') + end + + it 'swaps the columns' do + disable_migrations_output do + reversible_migration do |migration| + migration.before -> { + commit_user_mentions.reset_column_information + + expect(commit_user_mentions.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('integer') + expect(commit_user_mentions.columns.find { |c| c.name == 'note_id_convert_to_bigint' }.sql_type).to( + eq('bigint') + ) + } + + migration.after -> { + commit_user_mentions.reset_column_information + + expect(commit_user_mentions.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('bigint') + expect(commit_user_mentions.columns.find { |c| c.name == 'note_id_convert_to_bigint' }.sql_type).to( + eq('integer') + ) + } + end + end + end + end + end +end diff --git a/spec/migrations/swap_events_target_id_to_bigint_for_gitlab_dot_com_spec.rb b/spec/migrations/swap_events_target_id_to_bigint_for_gitlab_dot_com_spec.rb new file mode 100644 index 00000000000..a3dc73ecc38 --- /dev/null +++ b/spec/migrations/swap_events_target_id_to_bigint_for_gitlab_dot_com_spec.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe SwapEventsTargetIdToBigintForGitlabDotCom, feature_category: :database do + describe '#up' do + before do + # A we call `schema_migrate_down!` before each example, and for this migration + # `#down` is same as `#up`, we need to ensure we start from the expected state. + connection = described_class.new.connection + connection.execute('ALTER TABLE events ALTER COLUMN target_id TYPE integer') + connection.execute('ALTER TABLE events ALTER COLUMN target_id_convert_to_bigint TYPE bigint') + end + + # rubocop: disable RSpec/AnyInstanceOf + it 'swaps the integer and bigint columns for GitLab.com, dev, or test' do + allow_any_instance_of(described_class).to receive(:com_or_dev_or_test_but_not_jh?).and_return(true) + + events = table(:events) + + disable_migrations_output do + reversible_migration do |migration| + migration.before -> { + events.reset_column_information + + expect(events.columns.find { |c| c.name == 'target_id' }.sql_type).to eq('integer') + expect(events.columns.find { |c| c.name == 'target_id_convert_to_bigint' }.sql_type).to eq('bigint') + } + + migration.after -> { + events.reset_column_information + + expect(events.columns.find { |c| c.name == 'target_id' }.sql_type).to eq('bigint') + expect(events.columns.find { |c| c.name == 'target_id_convert_to_bigint' }.sql_type) + .to eq('integer') + } + end + end + end + + it 'is a no-op for other instances' do + allow_any_instance_of(described_class).to receive(:com_or_dev_or_test_but_not_jh?).and_return(false) + + events = table(:events) + + disable_migrations_output do + reversible_migration do |migration| + migration.before -> { + events.reset_column_information + + expect(events.columns.find { |c| c.name == 'target_id' }.sql_type).to eq('integer') + expect(events.columns.find { |c| c.name == 'target_id_convert_to_bigint' }.sql_type).to eq('bigint') + } + + migration.after -> { + events.reset_column_information + + expect(events.columns.find { |c| c.name == 'target_id' }.sql_type).to eq('integer') + expect(events.columns.find { |c| c.name == 'target_id_convert_to_bigint' }.sql_type).to eq('bigint') + } + end + end + end + # rubocop: enable RSpec/AnyInstanceOf + end +end diff --git a/spec/migrations/swap_todos_note_id_to_bigint_for_self_managed_spec.rb b/spec/migrations/swap_todos_note_id_to_bigint_for_self_managed_spec.rb new file mode 100644 index 00000000000..525e4fbcd8d --- /dev/null +++ b/spec/migrations/swap_todos_note_id_to_bigint_for_self_managed_spec.rb @@ -0,0 +1,159 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe SwapTodosNoteIdToBigintForSelfManaged, feature_category: :database do + describe '#up' do + context 'when GitLab.com, dev, or test' do + before do + # As we call `schema_migrate_down!` before each example, and for this migration + # `#down` is same as `#up`, we need to ensure we start from the expected state. + connection = described_class.new.connection + connection.execute('ALTER TABLE todos ALTER COLUMN note_id TYPE bigint') + connection.execute('ALTER TABLE todos DROP COLUMN IF EXISTS note_id_convert_to_bigint') + end + + it 'does not swap the columns' do + # rubocop: disable RSpec/AnyInstanceOf + allow_any_instance_of(described_class).to receive(:com_or_dev_or_test_but_not_jh?).and_return(true) + # rubocop: enable RSpec/AnyInstanceOf + + todos = table(:todos) + + disable_migrations_output do + reversible_migration do |migration| + migration.before -> { + todos.reset_column_information + + expect(todos.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('bigint') + expect(todos.columns.find { |c| c.name == 'note_id_convert_to_bigint' }).to be nil + } + + migration.after -> { + todos.reset_column_information + + expect(todos.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('bigint') + expect(todos.columns.find { |c| c.name == 'note_id_convert_to_bigint' }).to be nil + } + end + end + end + end + + context 'when self-managed instance with the columns already swapped' do + before do + # As we call `schema_migrate_down!` before each example, and for this migration + # `#down` is same as `#up`, we need to ensure we start from the expected state. + connection = described_class.new.connection + connection.execute('ALTER TABLE todos ALTER COLUMN note_id TYPE bigint') + connection.execute('ALTER TABLE todos ADD COLUMN IF NOT EXISTS note_id_convert_to_bigint integer') + end + + after do + connection = described_class.new.connection + connection.execute('ALTER TABLE todos DROP COLUMN IF EXISTS note_id_convert_to_bigint') + end + + it 'does not swap the columns' do + # rubocop: disable RSpec/AnyInstanceOf + allow_any_instance_of(described_class).to receive(:com_or_dev_or_test_but_not_jh?).and_return(false) + # rubocop: enable RSpec/AnyInstanceOf + + todos = table(:todos) + + migrate! + + expect(todos.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('bigint') + expect(todos.columns.find do |c| + c.name == 'note_id_convert_to_bigint' + end.sql_type).to eq('integer') + end + end + + context 'when self-managed instance with the `note_id_convert_to_bigint` column already dropped ' do + before do + # As we call `schema_migrate_down!` before each example, and for this migration + # `#down` is same as `#up`, we need to ensure we start from the expected state. + connection = described_class.new.connection + connection.execute('ALTER TABLE todos ALTER COLUMN note_id TYPE bigint') + connection.execute('ALTER TABLE todos DROP COLUMN IF EXISTS note_id_convert_to_bigint') + end + + it 'does not swap the columns' do + # rubocop: disable RSpec/AnyInstanceOf + allow_any_instance_of(described_class).to receive(:com_or_dev_or_test_but_not_jh?).and_return(false) + # rubocop: enable RSpec/AnyInstanceOf + + todos = table(:todos) + + disable_migrations_output do + reversible_migration do |migration| + migration.before -> { + todos.reset_column_information + + expect(todos.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('bigint') + expect(todos.columns.find { |c| c.name == 'note_id_convert_to_bigint' }).to be nil + } + + migration.after -> { + todos.reset_column_information + + expect(todos.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('bigint') + expect(todos.columns.find { |c| c.name == 'note_id_convert_to_bigint' }).to be nil + } + end + end + end + end + + context 'when self-managed instance' do + before do + # As we call `schema_migrate_down!` before each example, and for this migration + # `#down` is same as `#up`, we need to ensure we start from the expected state. + connection = described_class.new.connection + connection.execute('ALTER TABLE todos ALTER COLUMN note_id TYPE integer') + connection.execute('ALTER TABLE todos ADD COLUMN IF NOT EXISTS note_id_convert_to_bigint bigint') + connection.execute('ALTER TABLE todos ALTER COLUMN note_id_convert_to_bigint TYPE bigint') + connection.execute('DROP INDEX IF EXISTS index_todos_on_note_id_convert_to_bigint') + connection.execute('CREATE OR REPLACE FUNCTION trigger_dca935e3a712() RETURNS trigger LANGUAGE plpgsql AS $$ + BEGIN NEW."note_id_convert_to_bigint" := NEW."note_id"; RETURN NEW; END; $$;') + end + + after do + connection = described_class.new.connection + connection.execute('ALTER TABLE todos DROP COLUMN IF EXISTS note_id_convert_to_bigint') + end + + it 'swaps the columns' do + # rubocop: disable RSpec/AnyInstanceOf + allow_any_instance_of(described_class).to receive(:com_or_dev_or_test_but_not_jh?).and_return(false) + # rubocop: enable RSpec/AnyInstanceOf + + todos = table(:todos) + + disable_migrations_output do + reversible_migration do |migration| + migration.before -> { + todos.reset_column_information + + expect(todos.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('integer') + expect(todos.columns.find do |c| + c.name == 'note_id_convert_to_bigint' + end.sql_type).to eq('bigint') + } + + migration.after -> { + todos.reset_column_information + + expect(todos.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('bigint') + expect(todos.columns.find do |c| + c.name == 'note_id_convert_to_bigint' + end.sql_type).to eq('integer') + } + end + end + end + end + end +end |