summaryrefslogtreecommitdiff
path: root/spec/migrations
diff options
context:
space:
mode:
authorRobert Speicher <rspeicher@gmail.com>2021-01-20 13:34:23 -0600
committerRobert Speicher <rspeicher@gmail.com>2021-01-20 13:34:23 -0600
commit6438df3a1e0fb944485cebf07976160184697d72 (patch)
tree00b09bfd170e77ae9391b1a2f5a93ef6839f2597 /spec/migrations
parent42bcd54d971da7ef2854b896a7b34f4ef8601067 (diff)
downloadgitlab-ce-6438df3a1e0fb944485cebf07976160184697d72.tar.gz
Add latest changes from gitlab-org/gitlab@13-8-stable-eev13.8.0-rc42
Diffstat (limited to 'spec/migrations')
-rw-r--r--spec/migrations/20210112143418_remove_duplicate_services2_spec.rb52
-rw-r--r--spec/migrations/add_has_external_wiki_trigger_spec.rb128
-rw-r--r--spec/migrations/remove_alerts_service_records_spec.rb30
-rw-r--r--spec/migrations/remove_duplicate_labels_from_groups_spec.rb227
-rw-r--r--spec/migrations/schedule_populate_finding_uuid_for_vulnerability_feedback_spec.rb37
5 files changed, 474 insertions, 0 deletions
diff --git a/spec/migrations/20210112143418_remove_duplicate_services2_spec.rb b/spec/migrations/20210112143418_remove_duplicate_services2_spec.rb
new file mode 100644
index 00000000000..9a57d9bf84d
--- /dev/null
+++ b/spec/migrations/20210112143418_remove_duplicate_services2_spec.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20210112143418_remove_duplicate_services2.rb')
+
+RSpec.describe RemoveDuplicateServices2 do
+ let_it_be(:namespaces) { table(:namespaces) }
+ let_it_be(:projects) { table(:projects) }
+ let_it_be(:services) { table(:services) }
+
+ describe '#up' do
+ before do
+ stub_const("#{described_class}::BATCH_SIZE", 2)
+
+ namespaces.create!(id: 1, name: 'group', path: 'group')
+
+ projects.create!(id: 1, namespace_id: 1) # duplicate services
+ projects.create!(id: 2, namespace_id: 1) # normal services
+ projects.create!(id: 3, namespace_id: 1) # no services
+ projects.create!(id: 4, namespace_id: 1) # duplicate services
+ projects.create!(id: 5, namespace_id: 1) # duplicate services
+
+ services.create!(id: 1, project_id: 1, type: 'JiraService')
+ services.create!(id: 2, project_id: 1, type: 'JiraService')
+ services.create!(id: 3, project_id: 2, type: 'JiraService')
+ services.create!(id: 4, project_id: 4, type: 'AsanaService')
+ services.create!(id: 5, project_id: 4, type: 'AsanaService')
+ services.create!(id: 6, project_id: 4, type: 'JiraService')
+ services.create!(id: 7, project_id: 4, type: 'JiraService')
+ services.create!(id: 8, project_id: 4, type: 'SlackService')
+ services.create!(id: 9, project_id: 4, type: 'SlackService')
+ services.create!(id: 10, project_id: 5, type: 'JiraService')
+ services.create!(id: 11, project_id: 5, type: 'JiraService')
+
+ # Services without a project_id should be ignored
+ services.create!(id: 12, type: 'JiraService')
+ services.create!(id: 13, type: 'JiraService')
+ end
+
+ it 'schedules background jobs for all projects with duplicate services' do
+ Sidekiq::Testing.fake! do
+ freeze_time do
+ migrate!
+
+ expect(BackgroundMigrationWorker.jobs.size).to eq(2)
+ expect(described_class::MIGRATION).to be_scheduled_delayed_migration(2.minutes, 1, 4)
+ expect(described_class::MIGRATION).to be_scheduled_delayed_migration(4.minutes, 5)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/migrations/add_has_external_wiki_trigger_spec.rb b/spec/migrations/add_has_external_wiki_trigger_spec.rb
new file mode 100644
index 00000000000..10c6888c87e
--- /dev/null
+++ b/spec/migrations/add_has_external_wiki_trigger_spec.rb
@@ -0,0 +1,128 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+require_migration!
+
+RSpec.describe AddHasExternalWikiTrigger do
+ let(:migration) { described_class.new }
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:services) { table(:services) }
+
+ before do
+ @namespace = namespaces.create!(name: 'foo', path: 'foo')
+ @project = projects.create!(namespace_id: @namespace.id)
+ end
+
+ describe '#up' do
+ before do
+ migrate!
+ end
+
+ describe 'INSERT trigger' do
+ it 'sets `has_external_wiki` to true when active `ExternalWikiService` is inserted' do
+ expect do
+ services.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 service is for a different project' do
+ different_project = projects.create!(namespace_id: @namespace.id)
+
+ expect do
+ services.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
+ services.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 service is inserted' do
+ expect do
+ services.create!(type: 'MyService', active: true, project_id: @project.id)
+ end.not_to change { @project.reload.has_external_wiki }
+ end
+ end
+
+ describe 'UPDATE trigger' do
+ it 'sets `has_external_wiki` to true when `ExternalWikiService` is made active' do
+ service = services.create!(type: 'ExternalWikiService', active: false, project_id: @project.id)
+
+ expect do
+ service.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
+ service = services.create!(type: 'ExternalWikiService', active: true, project_id: @project.id)
+
+ expect do
+ service.update!(active: false)
+ end.to change { @project.reload.has_external_wiki }.to(false)
+ end
+
+ it 'does not change `has_external_wiki` when service is for a different project' do
+ different_project = projects.create!(namespace_id: @namespace.id)
+ service = services.create!(type: 'ExternalWikiService', active: false, project_id: different_project.id)
+
+ expect do
+ service.update!(active: true)
+ end.not_to change { @project.reload.has_external_wiki }
+ end
+ end
+
+ describe 'DELETE trigger' do
+ it 'sets `has_external_wiki` to false when `ExternalWikiService` is deleted' do
+ service = services.create!(type: 'ExternalWikiService', active: true, project_id: @project.id)
+
+ expect do
+ service.delete
+ end.to change { @project.reload.has_external_wiki }.to(false)
+ end
+
+ it 'does not change `has_external_wiki` when service is for a different project' do
+ different_project = projects.create!(namespace_id: @namespace.id)
+ service = services.create!(type: 'ExternalWikiService', active: true, project_id: different_project.id)
+
+ expect do
+ service.delete
+ end.not_to change { @project.reload.has_external_wiki }
+ end
+ end
+ end
+
+ describe '#down' do
+ before do
+ migration.up
+ migration.down
+ end
+
+ it 'drops the INSERT trigger' do
+ expect do
+ services.create!(type: 'ExternalWikiService', active: true, project_id: @project.id)
+ end.not_to change { @project.reload.has_external_wiki }
+ end
+
+ it 'drops the UPDATE trigger' do
+ service = services.create!(type: 'ExternalWikiService', active: false, project_id: @project.id)
+ @project.update!(has_external_wiki: false)
+
+ expect do
+ service.update!(active: true)
+ end.not_to change { @project.reload.has_external_wiki }
+ end
+
+ it 'drops the DELETE trigger' do
+ service = services.create!(type: 'ExternalWikiService', active: true, project_id: @project.id)
+ @project.update!(has_external_wiki: true)
+
+ expect do
+ service.delete
+ end.not_to change { @project.reload.has_external_wiki }
+ end
+ end
+end
diff --git a/spec/migrations/remove_alerts_service_records_spec.rb b/spec/migrations/remove_alerts_service_records_spec.rb
new file mode 100644
index 00000000000..eaf9f90b445
--- /dev/null
+++ b/spec/migrations/remove_alerts_service_records_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20210107194543_remove_alerts_service_records.rb')
+
+RSpec.describe RemoveAlertsServiceRecords do
+ let(:services) { table(:services) }
+ let(:alerts_service_data) { table(:alerts_service_data) }
+
+ before do
+ 5.times do
+ service = services.create!(type: 'AlertsService')
+ alerts_service_data.create!(service_id: service.id)
+ end
+
+ services.create!(type: 'SomeOtherType')
+ end
+
+ it 'removes services records of type AlertsService and corresponding data', :aggregate_failures do
+ expect(services.count).to eq(6)
+ expect(alerts_service_data.count).to eq(5)
+
+ migrate!
+
+ expect(services.count).to eq(1)
+ expect(services.first.type).to eq('SomeOtherType')
+ expect(services.where(type: 'AlertsService')).to be_empty
+ expect(alerts_service_data.all).to be_empty
+ end
+end
diff --git a/spec/migrations/remove_duplicate_labels_from_groups_spec.rb b/spec/migrations/remove_duplicate_labels_from_groups_spec.rb
new file mode 100644
index 00000000000..eff2c6a37e4
--- /dev/null
+++ b/spec/migrations/remove_duplicate_labels_from_groups_spec.rb
@@ -0,0 +1,227 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20200716234259_remove_duplicate_labels_from_group.rb')
+
+RSpec.describe RemoveDuplicateLabelsFromGroup do
+ let(:labels_table) { table(:labels) }
+ let(:labels) { labels_table.all }
+ let(:projects_table) { table(:projects) }
+ let(:projects) { projects_table.all }
+ let(:namespaces_table) { table(:namespaces) }
+ let(:namespaces) { namespaces_table.all }
+ let(:backup_labels_table) { table(:backup_labels) }
+ let(:backup_labels) { backup_labels_table.all }
+ # for those cases where we can't use the activerecord class because the `type` column
+ # makes it think it has polymorphism and should be/have a Label subclass
+ let(:sql_backup_labels) { ApplicationRecord.connection.execute('SELECT * from backup_labels') }
+
+ # all the possible tables with records that may have a relationship with a label
+ let(:analytics_cycle_analytics_group_stages_table) { table(:analytics_cycle_analytics_group_stages) }
+ let(:analytics_cycle_analytics_project_stages_table) { table(:analytics_cycle_analytics_project_stages) }
+ let(:board_labels_table) { table(:board_labels) }
+ let(:label_links_table) { table(:label_links) }
+ let(:label_priorities_table) { table(:label_priorities) }
+ let(:lists_table) { table(:lists) }
+ let(:resource_label_events_table) { table(:resource_label_events) }
+
+ let!(:group_one) { namespaces_table.create!(id: 1, type: 'Group', name: 'group', path: 'group') }
+ let!(:project_one) do
+ projects_table.create!(id: 1, name: 'project', path: 'project',
+ visibility_level: 0, namespace_id: group_one.id)
+ end
+
+ let(:label_title) { 'bug' }
+ let(:label_color) { 'red' }
+ let(:label_description) { 'nice label' }
+ let(:project_id) { project_one.id }
+ let(:group_id) { group_one.id }
+ let(:other_title) { 'feature' }
+
+ let(:group_label_attributes) do
+ {
+ title: label_title, color: label_color, group_id: group_id, type: 'GroupLabel', template: false, description: label_description
+ }
+ end
+
+ let(:migration) { described_class.new }
+
+ describe 'removing full duplicates' do
+ context 'when there are no duplicate labels' do
+ let!(:first_label) { labels_table.create!(group_label_attributes.merge(id: 1, title: "a different label")) }
+ let!(:second_label) { labels_table.create!(group_label_attributes.merge(id: 2, title: "a totally different label")) }
+
+ it 'does not remove anything' do
+ expect { migration.up }.not_to change { backup_labels_table.count }
+ end
+
+ it 'restores removed records when rolling back - no change' do
+ migration.up
+
+ expect { migration.down }.not_to change { labels_table.count }
+ end
+ end
+
+ context 'with duplicates with no relationships' do
+ let!(:first_label) { labels_table.create!(group_label_attributes.merge(id: 1)) }
+ let!(:second_label) { labels_table.create!(group_label_attributes.merge(id: 2)) }
+ let!(:third_label) { labels_table.create!(group_label_attributes.merge(id: 3, title: other_title)) }
+ let!(:fourth_label) { labels_table.create!(group_label_attributes.merge(id: 4, title: other_title)) }
+
+ it 'creates a backup record for each removed record' do
+ expect { migration.up }.to change { backup_labels_table.count }.from(0).to(2)
+ end
+
+ it 'creates the correct backup records with `create` restore_action' do
+ migration.up
+
+ expect(sql_backup_labels.find { |bl| bl["id"] == 2 }).to include(second_label.attributes.merge("restore_action" => described_class::CREATE, "new_title" => nil, "created_at" => anything, "updated_at" => anything))
+ expect(sql_backup_labels.find { |bl| bl["id"] == 4 }).to include(fourth_label.attributes.merge("restore_action" => described_class::CREATE, "new_title" => nil, "created_at" => anything, "updated_at" => anything))
+ end
+
+ it 'deletes all but one' do
+ migration.up
+
+ expect { second_label.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ expect { fourth_label.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+
+ it 'restores removed records on rollback' do
+ second_label_attributes = modified_attributes(second_label)
+ fourth_label_attributes = modified_attributes(fourth_label)
+
+ migration.up
+
+ migration.down
+
+ expect(second_label.attributes).to include(second_label_attributes)
+ expect(fourth_label.attributes).to include(fourth_label_attributes)
+ end
+ end
+
+ context 'two duplicate records, one of which has a relationship' do
+ let!(:first_label) { labels_table.create!(group_label_attributes.merge(id: 1)) }
+ let!(:second_label) { labels_table.create!(group_label_attributes.merge(id: 2)) }
+ let!(:label_priority) { label_priorities_table.create!(label_id: second_label.id, project_id: project_id, priority: 1) }
+
+ it 'does not remove anything' do
+ expect { migration.up }.not_to change { labels_table.count }
+ end
+
+ it 'does not create a backup record with `create` restore_action' do
+ expect { migration.up }.not_to change { backup_labels_table.where(restore_action: described_class::CREATE).count }
+ end
+
+ it 'restores removed records when rolling back - no change' do
+ migration.up
+
+ expect { migration.down }.not_to change { labels_table.count }
+ end
+ end
+
+ context 'multiple duplicates, a subset of which have relationships' do
+ let!(:first_label) { labels_table.create!(group_label_attributes.merge(id: 1)) }
+ let!(:second_label) { labels_table.create!(group_label_attributes.merge(id: 2)) }
+ let!(:label_priority_for_second_label) { label_priorities_table.create!(label_id: second_label.id, project_id: project_id, priority: 1) }
+ let!(:third_label) { labels_table.create!(group_label_attributes.merge(id: 3)) }
+ let!(:fourth_label) { labels_table.create!(group_label_attributes.merge(id: 4)) }
+ let!(:label_priority_for_fourth_label) { label_priorities_table.create!(label_id: fourth_label.id, project_id: project_id, priority: 2) }
+
+ it 'creates a backup record with `create` restore_action for each removed record' do
+ expect { migration.up }.to change { backup_labels_table.where(restore_action: described_class::CREATE).count }.from(0).to(1)
+ end
+
+ it 'creates the correct backup records' do
+ migration.up
+
+ expect(sql_backup_labels.find { |bl| bl["id"] == 3 }).to include(third_label.attributes.merge("restore_action" => described_class::CREATE, "new_title" => nil, "created_at" => anything, "updated_at" => anything))
+ end
+
+ it 'deletes the duplicate record' do
+ migration.up
+
+ expect { first_label.reload }.not_to raise_error
+ expect { second_label.reload }.not_to raise_error
+ expect { third_label.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+
+ it 'restores removed records on rollback' do
+ third_label_attributes = modified_attributes(third_label)
+
+ migration.up
+ migration.down
+
+ expect(third_label.attributes).to include(third_label_attributes)
+ end
+ end
+ end
+
+ describe 'renaming partial duplicates' do
+ # partial duplicates - only group_id and title match. Distinct colour prevents deletion.
+ context 'when there are no duplicate labels' do
+ let!(:first_label) { labels_table.create!(group_label_attributes.merge(id: 1, title: "a unique label", color: 'green')) }
+ let!(:second_label) { labels_table.create!(group_label_attributes.merge(id: 2, title: "a totally different, unique, label", color: 'blue')) }
+
+ it 'does not rename anything' do
+ expect { migration.up }.not_to change { backup_labels_table.count }
+ end
+ end
+
+ context 'with duplicates with no relationships' do
+ let!(:first_label) { labels_table.create!(group_label_attributes.merge(id: 1, color: 'green')) }
+ let!(:second_label) { labels_table.create!(group_label_attributes.merge(id: 2, color: 'blue')) }
+ let!(:third_label) { labels_table.create!(group_label_attributes.merge(id: 3, title: other_title, color: 'purple')) }
+ let!(:fourth_label) { labels_table.create!(group_label_attributes.merge(id: 4, title: other_title, color: 'yellow')) }
+
+ it 'creates a backup record for each renamed record' do
+ expect { migration.up }.to change { backup_labels_table.count }.from(0).to(2)
+ end
+
+ it 'creates the correct backup records with `rename` restore_action' do
+ migration.up
+
+ expect(sql_backup_labels.find { |bl| bl["id"] == 2 }).to include(second_label.attributes.merge("restore_action" => described_class::RENAME, "created_at" => anything, "updated_at" => anything))
+ expect(sql_backup_labels.find { |bl| bl["id"] == 4 }).to include(fourth_label.attributes.merge("restore_action" => described_class::RENAME, "created_at" => anything, "updated_at" => anything))
+ end
+
+ it 'modifies the titles of the partial duplicates' do
+ migration.up
+
+ expect(second_label.reload.title).to match(/#{label_title}_duplicate#{second_label.id}$/)
+ expect(fourth_label.reload.title).to match(/#{other_title}_duplicate#{fourth_label.id}$/)
+ end
+
+ it 'restores renamed records on rollback' do
+ second_label_attributes = modified_attributes(second_label)
+ fourth_label_attributes = modified_attributes(fourth_label)
+
+ migration.up
+
+ migration.down
+
+ expect(second_label.reload.attributes).to include(second_label_attributes)
+ expect(fourth_label.reload.attributes).to include(fourth_label_attributes)
+ end
+
+ context 'when the labels have a long title that might overflow' do
+ let(:long_title) { "a" * 255 }
+
+ before do
+ first_label.update_attribute(:title, long_title)
+ second_label.update_attribute(:title, long_title)
+ end
+
+ it 'keeps the length within the limit' do
+ migration.up
+
+ expect(second_label.reload.title).to eq("#{"a" * 244}_duplicate#{second_label.id}")
+ expect(second_label.title.length).to eq(255)
+ end
+ end
+ end
+ end
+
+ def modified_attributes(label)
+ label.attributes.except('created_at', 'updated_at')
+ end
+end
diff --git a/spec/migrations/schedule_populate_finding_uuid_for_vulnerability_feedback_spec.rb b/spec/migrations/schedule_populate_finding_uuid_for_vulnerability_feedback_spec.rb
new file mode 100644
index 00000000000..d8bdefd5546
--- /dev/null
+++ b/spec/migrations/schedule_populate_finding_uuid_for_vulnerability_feedback_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe SchedulePopulateFindingUuidForVulnerabilityFeedback do
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:users) { table(:users) }
+ let(:vulnerability_feedback) { table(:vulnerability_feedback) }
+
+ let(:namespace) { namespaces.create!(name: 'gitlab', path: 'gitlab-org') }
+ let(:project) { projects.create!(namespace_id: namespace.id, name: 'foo') }
+ let(:user) { users.create!(username: 'john.doe', projects_limit: 1) }
+
+ let(:common_feedback_params) { { feedback_type: 0, category: 0, project_id: project.id, author_id: user.id } }
+ let!(:feedback_1) { vulnerability_feedback.create!(**common_feedback_params, project_fingerprint: 'foo') }
+ let!(:feedback_2) { vulnerability_feedback.create!(**common_feedback_params, project_fingerprint: 'bar') }
+ let!(:feedback_3) { vulnerability_feedback.create!(**common_feedback_params, project_fingerprint: 'zoo', finding_uuid: SecureRandom.uuid) }
+
+ around do |example|
+ freeze_time { Sidekiq::Testing.fake! { example.run } }
+ end
+
+ before do
+ stub_const("#{described_class.name}::BATCH_SIZE", 1)
+ end
+
+ it 'schedules the background jobs', :aggregate_failures do
+ migrate!
+
+ expect(BackgroundMigrationWorker.jobs.size).to be(3)
+ expect(described_class::MIGRATION_CLASS).to be_scheduled_delayed_migration(2.minutes, feedback_1.id, feedback_1.id)
+ expect(described_class::MIGRATION_CLASS).to be_scheduled_delayed_migration(4.minutes, feedback_2.id, feedback_2.id)
+ expect(described_class::MIGRATION_CLASS).to be_scheduled_delayed_migration(6.minutes, feedback_3.id, feedback_3.id)
+ end
+end