diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-02-18 09:45:46 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-02-18 09:45:46 +0000 |
commit | a7b3560714b4d9cc4ab32dffcd1f74a284b93580 (patch) | |
tree | 7452bd5c3545c2fa67a28aa013835fb4fa071baf /spec/migrations | |
parent | ee9173579ae56a3dbfe5afe9f9410c65bb327ca7 (diff) | |
download | gitlab-ce-a7b3560714b4d9cc4ab32dffcd1f74a284b93580.tar.gz |
Add latest changes from gitlab-org/gitlab@14-8-stable-eev14.8.0-rc42
Diffstat (limited to 'spec/migrations')
19 files changed, 1145 insertions, 4 deletions
diff --git a/spec/migrations/20220106111958_add_insert_or_update_vulnerability_reads_trigger_spec.rb b/spec/migrations/20220106111958_add_insert_or_update_vulnerability_reads_trigger_spec.rb new file mode 100644 index 00000000000..3e450546315 --- /dev/null +++ b/spec/migrations/20220106111958_add_insert_or_update_vulnerability_reads_trigger_spec.rb @@ -0,0 +1,151 @@ +# frozen_string_literal: true + +require 'spec_helper' + +require_migration! + +RSpec.describe AddInsertOrUpdateVulnerabilityReadsTrigger do + let(:migration) { described_class.new } + let(:vulnerabilities) { table(:vulnerabilities) } + let(:vulnerability_reads) { table(:vulnerability_reads) } + 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) } + let(:scanner) { table(:vulnerability_scanners).create!(project_id: project.id, external_id: 'test 1', name: 'test scanner 1') } + + let(:vulnerability) do + create_vulnerability!( + project_id: project.id, + author_id: user.id + ) + end + + let(:vulnerability2) 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(:finding) do + create_finding!( + project_id: project.id, + scanner_id: scanner.id, + primary_identifier_id: identifier.id + ) + end + + describe '#up' do + before do + migrate! + end + + describe 'UPDATE trigger' do + context 'when vulnerability_id is updated' do + it 'creates a new vulnerability_reads row' do + expect do + finding.update!(vulnerability_id: vulnerability.id) + end.to change { vulnerability_reads.count }.from(0).to(1) + end + end + + context 'when vulnerability_id is not updated' do + it 'does not create a new vulnerability_reads row' do + finding.update!(vulnerability_id: nil) + + expect do + finding.update!(location: '') + end.not_to change { vulnerability_reads.count } + end + end + end + + describe 'INSERT trigger' do + context 'when vulnerability_id is set' do + it 'creates a new vulnerability_reads row' do + expect do + create_finding!( + vulnerability_id: vulnerability2.id, + project_id: project.id, + scanner_id: scanner.id, + primary_identifier_id: identifier.id + ) + end.to change { vulnerability_reads.count }.from(0).to(1) + end + end + + context 'when vulnerability_id is not set' do + it 'does not create a new vulnerability_reads row' do + expect do + create_finding!( + project_id: project.id, + scanner_id: scanner.id, + primary_identifier_id: identifier.id + ) + end.not_to change { vulnerability_reads.count } + end + end + end + end + + describe '#down' do + before do + migration.up + migration.down + end + + it 'drops the trigger' do + expect do + finding.update!(vulnerability_id: vulnerability.id) + end.not_to change { vulnerability_reads.count } + 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 + + # rubocop:disable Metrics/ParameterLists + def create_finding!( + vulnerability_id: nil, 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/20220106112043_add_update_vulnerability_reads_trigger_spec.rb b/spec/migrations/20220106112043_add_update_vulnerability_reads_trigger_spec.rb new file mode 100644 index 00000000000..d988b1e42b9 --- /dev/null +++ b/spec/migrations/20220106112043_add_update_vulnerability_reads_trigger_spec.rb @@ -0,0 +1,128 @@ +# frozen_string_literal: true + +require 'spec_helper' + +require_migration! + +RSpec.describe AddUpdateVulnerabilityReadsTrigger do + let(:migration) { described_class.new } + let(:vulnerability_reads) { table(:vulnerability_reads) } + let(:issue_links) { table(:vulnerability_issue_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) } + let(:issue) { table(:issues).create!(description: '1234', state_id: 1, project_id: project.id) } + let(:scanner) { table(:vulnerability_scanners).create!(project_id: project.id, external_id: 'test 1', name: 'test scanner 1') } + + let(:vulnerability) do + create_vulnerability!( + project_id: project.id, + report_type: 7, + 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 + + describe '#up' do + before do + migrate! + end + + describe 'UPDATE trigger' do + before do + create_finding!( + vulnerability_id: vulnerability.id, + project_id: project.id, + scanner_id: scanner.id, + report_type: 7, + primary_identifier_id: identifier.id + ) + end + + context 'when vulnerability attributes are updated' do + it 'updates vulnerability attributes in vulnerability_reads' do + expect do + vulnerability.update!(severity: 6) + end.to change { vulnerability_reads.first.severity }.from(7).to(6) + end + end + + context 'when vulnerability attributes are not updated' do + it 'does not update vulnerability attributes in vulnerability_reads' do + expect do + vulnerability.update!(title: "New vulnerability") + end.not_to change { vulnerability_reads.first } + end + end + end + end + + describe '#down' do + before do + migration.up + migration.down + create_finding!( + vulnerability_id: vulnerability.id, + project_id: project.id, + scanner_id: scanner.id, + report_type: 7, + primary_identifier_id: identifier.id + ) + end + + it 'drops the trigger' do + expect do + vulnerability.update!(severity: 6) + end.not_to change { vulnerability_reads.first.severity } + 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 + + # rubocop:disable Metrics/ParameterLists + def create_finding!( + vulnerability_id: nil, 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/20220106112085_add_update_vulnerability_reads_location_trigger_spec.rb b/spec/migrations/20220106112085_add_update_vulnerability_reads_location_trigger_spec.rb new file mode 100644 index 00000000000..901f1cf6041 --- /dev/null +++ b/spec/migrations/20220106112085_add_update_vulnerability_reads_location_trigger_spec.rb @@ -0,0 +1,136 @@ +# frozen_string_literal: true + +require 'spec_helper' + +require_migration! + +RSpec.describe AddUpdateVulnerabilityReadsLocationTrigger do + let(:migration) { described_class.new } + let(:vulnerability_reads) { table(:vulnerability_reads) } + let(:issue_links) { table(:vulnerability_issue_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) } + let(:issue) { table(:issues).create!(description: '1234', state_id: 1, project_id: project.id) } + let(:scanner) { table(:vulnerability_scanners).create!(project_id: project.id, external_id: 'test 1', name: 'test scanner 1') } + + let(:vulnerability) do + create_vulnerability!( + project_id: project.id, + report_type: 7, + 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 + + describe '#up' do + before do + migrate! + end + + describe 'UPDATE trigger' do + context 'when image is updated' do + it 'updates location_image in vulnerability_reads' do + finding = create_finding!( + vulnerability_id: vulnerability.id, + project_id: project.id, + scanner_id: scanner.id, + report_type: 7, + location: { "image" => "alpine:3.4" }, + primary_identifier_id: identifier.id + ) + + expect do + finding.update!(location: { "image" => "alpine:4", "kubernetes_resource" => { "agent_id" => "1234" } }) + end.to change { vulnerability_reads.first.location_image }.from("alpine:3.4").to("alpine:4") + end + end + + context 'when image is not updated' do + it 'updates location_image in vulnerability_reads' do + finding = create_finding!( + vulnerability_id: vulnerability.id, + project_id: project.id, + scanner_id: scanner.id, + report_type: 7, + location: { "image" => "alpine:3.4", "kubernetes_resource" => { "agent_id" => "1234" } }, + primary_identifier_id: identifier.id + ) + + expect do + finding.update!(project_fingerprint: "123qweasdzx") + end.not_to change { vulnerability_reads.first.location_image } + end + end + end + end + + describe '#down' do + before do + migration.up + migration.down + end + + it 'drops the trigger' do + finding = create_finding!( + vulnerability_id: vulnerability.id, + project_id: project.id, + scanner_id: scanner.id, + primary_identifier_id: identifier.id + ) + + expect do + finding.update!(location: '{"image":"alpine:4"}') + end.not_to change { vulnerability_reads.first.location_image } + 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 + + # rubocop:disable Metrics/ParameterLists + def create_finding!( + vulnerability_id: nil, 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/20220106163326_add_has_issues_on_vulnerability_reads_trigger_spec.rb b/spec/migrations/20220106163326_add_has_issues_on_vulnerability_reads_trigger_spec.rb new file mode 100644 index 00000000000..8e50b74eb9c --- /dev/null +++ b/spec/migrations/20220106163326_add_has_issues_on_vulnerability_reads_trigger_spec.rb @@ -0,0 +1,134 @@ +# frozen_string_literal: true + +require 'spec_helper' + +require_migration! + +RSpec.describe AddHasIssuesOnVulnerabilityReadsTrigger do + let(:migration) { described_class.new } + let(:vulnerability_reads) { table(:vulnerability_reads) } + let(:issue_links) { table(:vulnerability_issue_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) } + let(:issue) { table(:issues).create!(description: '1234', state_id: 1, project_id: project.id) } + let(:scanner) { table(:vulnerability_scanners).create!(project_id: project.id, external_id: 'test 1', name: 'test scanner 1') } + + 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 + + before do + create_finding!( + vulnerability_id: vulnerability.id, + project_id: project.id, + scanner_id: scanner.id, + primary_identifier_id: identifier.id + ) + + @vulnerability_read = vulnerability_reads.first + end + + describe '#up' do + before do + migrate! + end + + describe 'INSERT trigger' do + it 'updates has_issues in vulnerability_reads' do + expect do + issue_links.create!(vulnerability_id: vulnerability.id, issue_id: issue.id) + end.to change { @vulnerability_read.reload.has_issues }.from(false).to(true) + end + end + + describe 'DELETE trigger' do + let(:issue2) { table(:issues).create!(description: '1234', state_id: 1, project_id: project.id) } + + it 'does not change has_issues when there exists another issue' do + issue_link1 = issue_links.create!(vulnerability_id: vulnerability.id, issue_id: issue.id) + issue_links.create!(vulnerability_id: vulnerability.id, issue_id: issue2.id) + + expect do + issue_link1.delete + end.not_to change { @vulnerability_read.reload.has_issues } + end + + it 'unsets has_issues when all issues are deleted' do + issue_link1 = issue_links.create!(vulnerability_id: vulnerability.id, issue_id: issue.id) + issue_link2 = issue_links.create!(vulnerability_id: vulnerability.id, issue_id: issue2.id) + + expect do + issue_link1.delete + issue_link2.delete + end.to change { @vulnerability_read.reload.has_issues }.from(true).to(false) + end + end + end + + describe '#down' do + before do + migration.up + migration.down + end + + it 'drops the trigger' do + expect do + issue_links.create!(vulnerability_id: vulnerability.id, issue_id: issue.id) + end.not_to change { @vulnerability_read.has_issues } + 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 + + # rubocop:disable Metrics/ParameterLists + def create_finding!( + vulnerability_id: nil, 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/20220107064845_populate_vulnerability_reads_spec.rb b/spec/migrations/20220107064845_populate_vulnerability_reads_spec.rb new file mode 100644 index 00000000000..ece971a50c9 --- /dev/null +++ b/spec/migrations/20220107064845_populate_vulnerability_reads_spec.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true +require 'spec_helper' + +require_migration! + +RSpec.describe PopulateVulnerabilityReads, :migration do + let_it_be(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') } + let_it_be(:user) { table(:users).create!(email: 'author@example.com', username: 'author', projects_limit: 10) } + let_it_be(:project) { table(:projects).create!(namespace_id: namespace.id) } + let_it_be(:scanner) { table(:vulnerability_scanners).create!(project_id: project.id, external_id: 'test 1', name: 'test scanner 1') } + let_it_be(:background_migration_jobs) { table(:background_migration_jobs) } + let_it_be(:vulnerabilities) { table(:vulnerabilities) } + let_it_be(:vulnerability_reads) { table(:vulnerability_reads) } + let_it_be(:vulnerabilities_findings) { table(:vulnerability_occurrences) } + let_it_be(:vulnerability_issue_links) { table(:vulnerability_issue_links) } + let_it_be(:vulnerability_ids) { [] } + + before do + stub_const("#{described_class}::BATCH_SIZE", 1) + stub_const("#{described_class}::SUB_BATCH_SIZE", 1) + + 5.times.each do |x| + vulnerability = create_vulnerability!( + project_id: project.id, + report_type: 7, + author_id: user.id + ) + identifier = table(:vulnerability_identifiers).create!( + project_id: project.id, + external_type: 'uuid-v5', + external_id: 'uuid-v5', + fingerprint: Digest::SHA1.hexdigest("#{vulnerability.id}"), + name: 'Identifier for UUIDv5') + + create_finding!( + vulnerability_id: vulnerability.id, + project_id: project.id, + scanner_id: scanner.id, + primary_identifier_id: identifier.id + ) + + vulnerability_ids << vulnerability.id + end + end + + around do |example| + freeze_time { Sidekiq::Testing.fake! { example.run } } + end + + it 'schedules background migrations' do + migrate! + + expect(background_migration_jobs.count).to eq(5) + expect(background_migration_jobs.first.arguments).to match_array([vulnerability_ids.first, vulnerability_ids.first, 1]) + expect(background_migration_jobs.second.arguments).to match_array([vulnerability_ids.second, vulnerability_ids.second, 1]) + expect(background_migration_jobs.third.arguments).to match_array([vulnerability_ids.third, vulnerability_ids.third, 1]) + expect(background_migration_jobs.fourth.arguments).to match_array([vulnerability_ids.fourth, vulnerability_ids.fourth, 1]) + expect(background_migration_jobs.fifth.arguments).to match_array([vulnerability_ids.fifth, vulnerability_ids.fifth, 1]) + + expect(BackgroundMigrationWorker.jobs.size).to eq(5) + expect(described_class::MIGRATION_NAME).to be_scheduled_delayed_migration(2.minutes, vulnerability_ids.first, vulnerability_ids.first, 1) + expect(described_class::MIGRATION_NAME).to be_scheduled_delayed_migration(4.minutes, vulnerability_ids.second, vulnerability_ids.second, 1) + expect(described_class::MIGRATION_NAME).to be_scheduled_delayed_migration(6.minutes, vulnerability_ids.third, vulnerability_ids.third, 1) + expect(described_class::MIGRATION_NAME).to be_scheduled_delayed_migration(8.minutes, vulnerability_ids.fourth, vulnerability_ids.fourth, 1) + expect(described_class::MIGRATION_NAME).to be_scheduled_delayed_migration(10.minutes, vulnerability_ids.fifth, vulnerability_ids.fifth, 1) + 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 + + # rubocop:disable Metrics/ParameterLists + def create_finding!( + id: nil, + vulnerability_id:, project_id:, scanner_id:, primary_identifier_id:, + name: "test", severity: 7, confidence: 7, report_type: 0, + project_fingerprint: '123qweasdzxc', location_fingerprint: 'test', + metadata_version: 'test', raw_metadata: 'test', uuid: SecureRandom.uuid) + params = { + 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_fingerprint: location_fingerprint, + metadata_version: metadata_version, + raw_metadata: raw_metadata, + uuid: uuid + } + params[:id] = id unless id.nil? + vulnerabilities_findings.create!(params) + end + # rubocop:enable Metrics/ParameterLists +end diff --git a/spec/migrations/20220120094340_drop_position_from_security_findings_spec.rb b/spec/migrations/20220120094340_drop_position_from_security_findings_spec.rb new file mode 100644 index 00000000000..2ad9a8220c3 --- /dev/null +++ b/spec/migrations/20220120094340_drop_position_from_security_findings_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration!('drop_position_from_security_findings') + +RSpec.describe DropPositionFromSecurityFindings do + let(:events) { table(:security_findings) } + + it 'correctly migrates up and down' do + reversible_migration do |migration| + migration.before -> { + expect(events.column_names).to include('position') + } + + migration.after -> { + events.reset_column_information + expect(events.column_names).not_to include('position') + } + end + end +end diff --git a/spec/migrations/20220124130028_dedup_runner_projects_spec.rb b/spec/migrations/20220124130028_dedup_runner_projects_spec.rb new file mode 100644 index 00000000000..2698af6f6f5 --- /dev/null +++ b/spec/migrations/20220124130028_dedup_runner_projects_spec.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20220124130028_dedup_runner_projects.rb') + +RSpec.describe DedupRunnerProjects, :migration, schema: 20220120085655 do + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + let(:runners) { table(:ci_runners) } + let(:runner_projects) { table(:ci_runner_projects) } + + let!(:namespace) { namespaces.create!(name: 'foo', path: 'foo') } + let!(:project) { projects.create!(namespace_id: namespace.id) } + let!(:project_2) { projects.create!(namespace_id: namespace.id) } + let!(:runner) { runners.create!(runner_type: 'project_type') } + let!(:runner_2) { runners.create!(runner_type: 'project_type') } + let!(:runner_3) { runners.create!(runner_type: 'project_type') } + + let!(:duplicated_runner_project_1) { runner_projects.create!(runner_id: runner.id, project_id: project.id) } + let!(:duplicated_runner_project_2) { runner_projects.create!(runner_id: runner.id, project_id: project.id) } + let!(:duplicated_runner_project_3) { runner_projects.create!(runner_id: runner_2.id, project_id: project_2.id) } + let!(:duplicated_runner_project_4) { runner_projects.create!(runner_id: runner_2.id, project_id: project_2.id) } + + let!(:non_duplicated_runner_project) { runner_projects.create!(runner_id: runner_3.id, project_id: project.id) } + + it 'deduplicates ci_runner_projects table' do + expect { migrate! }.to change { runner_projects.count }.from(5).to(3) + end + + it 'merges `duplicated_runner_project_1` with `duplicated_runner_project_2`', :aggregate_failures do + migrate! + + expect(runner_projects.where(id: duplicated_runner_project_1.id)).not_to(exist) + + merged_runner_projects = runner_projects.find_by(id: duplicated_runner_project_2.id) + + expect(merged_runner_projects).to be_present + expect(merged_runner_projects.created_at).to be_like_time(duplicated_runner_project_1.created_at) + expect(merged_runner_projects.created_at).to be_like_time(duplicated_runner_project_2.created_at) + end + + it 'merges `duplicated_runner_project_3` with `duplicated_runner_project_4`', :aggregate_failures do + migrate! + + expect(runner_projects.where(id: duplicated_runner_project_3.id)).not_to(exist) + + merged_runner_projects = runner_projects.find_by(id: duplicated_runner_project_4.id) + + expect(merged_runner_projects).to be_present + expect(merged_runner_projects.created_at).to be_like_time(duplicated_runner_project_3.created_at) + expect(merged_runner_projects.created_at).to be_like_time(duplicated_runner_project_4.created_at) + end + + it 'does not change non duplicated records' do + expect { migrate! }.not_to change { non_duplicated_runner_project.reload.attributes } + end + + it 'does nothing when there are no runner projects' do + runner_projects.delete_all + + migrate! + + expect(runner_projects.count).to eq(0) + end +end diff --git a/spec/migrations/20220128155251_remove_dangling_running_builds_spec.rb b/spec/migrations/20220128155251_remove_dangling_running_builds_spec.rb new file mode 100644 index 00000000000..a48464befdf --- /dev/null +++ b/spec/migrations/20220128155251_remove_dangling_running_builds_spec.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration!('remove_dangling_running_builds') + +RSpec.describe RemoveDanglingRunningBuilds do + let(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') } + let(:project) { table(:projects).create!(namespace_id: namespace.id) } + let(:runner) { table(:ci_runners).create!(runner_type: 1) } + let(:builds) { table(:ci_builds) } + let(:running_builds) { table(:ci_running_builds) } + + let(:running_build) do + builds.create!( + name: 'test 1', + status: 'running', + project_id: project.id, + type: 'Ci::Build') + end + + let(:failed_build) do + builds.create!( + name: 'test 2', + status: 'failed', + project_id: project.id, + type: 'Ci::Build') + end + + let!(:running_metadata) do + running_builds.create!( + build_id: running_build.id, + project_id: project.id, + runner_id: runner.id, + runner_type: + runner.runner_type) + end + + let!(:failed_metadata) do + running_builds.create!( + build_id: failed_build.id, + project_id: project.id, + runner_id: runner.id, + runner_type: runner.runner_type) + end + + it 'removes failed builds' do + migrate! + + expect(running_metadata.reload).to be_present + expect { failed_metadata.reload } .to raise_error(ActiveRecord::RecordNotFound) + end +end diff --git a/spec/migrations/20220128155814_fix_approval_rules_code_owners_rule_type_index_spec.rb b/spec/migrations/20220128155814_fix_approval_rules_code_owners_rule_type_index_spec.rb new file mode 100644 index 00000000000..1558facdf96 --- /dev/null +++ b/spec/migrations/20220128155814_fix_approval_rules_code_owners_rule_type_index_spec.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration!('fix_approval_rules_code_owners_rule_type_index') + +RSpec.describe FixApprovalRulesCodeOwnersRuleTypeIndex, :migration do + let(:table_name) { :approval_merge_request_rules } + let(:index_name) { 'index_approval_rules_code_owners_rule_type' } + + it 'correctly migrates up and down' do + reversible_migration do |migration| + migration.before -> { + expect(subject.index_exists_by_name?(table_name, index_name)).to be_truthy + } + + migration.after -> { + expect(subject.index_exists_by_name?(table_name, index_name)).to be_truthy + } + end + end + + context 'when the index already exists' do + before do + subject.add_concurrent_index table_name, :merge_request_id, where: 'rule_type = 2', name: index_name + end + + it 'keeps the index' do + migrate! + + expect(subject.index_exists_by_name?(table_name, index_name)).to be_truthy + end + end +end diff --git a/spec/migrations/20220202105733_delete_service_template_records_spec.rb b/spec/migrations/20220202105733_delete_service_template_records_spec.rb new file mode 100644 index 00000000000..c9f6b5cbe66 --- /dev/null +++ b/spec/migrations/20220202105733_delete_service_template_records_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true +require 'spec_helper' + +require_migration! + +RSpec.describe DeleteServiceTemplateRecords do + let(:integrations) { table(:integrations) } + let(:chat_names) { table(:chat_names) } + let(:web_hooks) { table(:web_hooks) } + let(:slack_integrations) { table(:slack_integrations) } + let(:zentao_tracker_data) { table(:zentao_tracker_data) } + let(:jira_tracker_data) { table(:jira_tracker_data) } + let(:issue_tracker_data) { table(:issue_tracker_data) } + + before do + template = integrations.create!(template: true) + chat_names.create!(service_id: template.id, user_id: 1, team_id: 1, chat_id: 1) + web_hooks.create!(service_id: template.id) + slack_integrations.create!(service_id: template.id, team_id: 1, team_name: 'team', alias: 'alias', user_id: 1) + zentao_tracker_data.create!(integration_id: template.id) + jira_tracker_data.create!(service_id: template.id) + issue_tracker_data.create!(service_id: template.id) + + integrations.create!(template: false) + end + + it 'deletes template records and associated data' do + expect { migrate! } + .to change { integrations.where(template: true).count }.from(1).to(0) + .and change { chat_names.count }.from(1).to(0) + .and change { web_hooks.count }.from(1).to(0) + .and change { slack_integrations.count }.from(1).to(0) + .and change { zentao_tracker_data.count }.from(1).to(0) + .and change { jira_tracker_data.count }.from(1).to(0) + .and change { issue_tracker_data.count }.from(1).to(0) + end + + it 'does not delete non template records' do + expect { migrate! } + .not_to change { integrations.where(template: false).count } + end +end diff --git a/spec/migrations/20220211214605_update_integrations_trigger_type_new_on_insert_null_safe_spec.rb b/spec/migrations/20220211214605_update_integrations_trigger_type_new_on_insert_null_safe_spec.rb new file mode 100644 index 00000000000..bf79ee02ff1 --- /dev/null +++ b/spec/migrations/20220211214605_update_integrations_trigger_type_new_on_insert_null_safe_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'spec_helper' + +require_migration! + +RSpec.describe UpdateIntegrationsTriggerTypeNewOnInsertNullSafe, :migration do + let(:integrations) { table(:integrations) } + + before do + migrate! + end + + it 'leaves defined values alone' do + record = integrations.create!(type: 'XService', type_new: 'Integrations::Y') + + expect(integrations.find(record.id)).to have_attributes(type: 'XService', type_new: 'Integrations::Y') + end + + it 'keeps type_new synchronized with type' do + record = integrations.create!(type: 'AbcService', type_new: nil) + + expect(integrations.find(record.id)).to have_attributes( + type: 'AbcService', + type_new: 'Integrations::Abc' + ) + end + + it 'keeps type synchronized with type_new' do + record = integrations.create!(type: nil, type_new: 'Integrations::Abc') + + expect(integrations.find(record.id)).to have_attributes( + type: 'AbcService', + type_new: 'Integrations::Abc' + ) + end +end diff --git a/spec/migrations/backfill_namespace_id_for_namespace_routes_spec.rb b/spec/migrations/backfill_namespace_id_for_namespace_routes_spec.rb new file mode 100644 index 00000000000..913ec404795 --- /dev/null +++ b/spec/migrations/backfill_namespace_id_for_namespace_routes_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe BackfillNamespaceIdForNamespaceRoutes do + let_it_be(:migration) { described_class::MIGRATION } + + describe '#up' do + it 'schedules background jobs for each batch of routes' do + migrate! + + expect(migration).to have_scheduled_batched_migration( + table_name: :routes, + column_name: :id, + interval: described_class::INTERVAL + ) + end + end + + describe '#down' do + it 'deletes all batched migration records' do + migrate! + schema_migrate_down! + + expect(migration).not_to have_scheduled_batched_migration + end + end +end diff --git a/spec/migrations/backfill_project_namespaces_for_group_spec.rb b/spec/migrations/backfill_project_namespaces_for_group_spec.rb new file mode 100644 index 00000000000..0d34d19d42a --- /dev/null +++ b/spec/migrations/backfill_project_namespaces_for_group_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe BackfillProjectNamespacesForGroup do + let_it_be(:migration) { described_class::MIGRATION } + + let(:projects) { table(:projects) } + let(:namespaces) { table(:namespaces) } + let(:parent_group1) { namespaces.create!(name: 'parent_group1', path: 'parent_group1', visibility_level: 20, type: 'Group') } + let!(:parent_group1_project) { projects.create!(name: 'parent_group1_project', path: 'parent_group1_project', namespace_id: parent_group1.id, visibility_level: 20) } + + before do + allow(Gitlab).to receive(:com?).and_return(true) + end + + describe '#up' do + before do + stub_const("BackfillProjectNamespacesForGroup::GROUP_ID", parent_group1.id) + end + + it 'schedules background jobs for each batch of namespaces' do + migrate! + + expect(migration).to have_scheduled_batched_migration( + table_name: :projects, + column_name: :id, + job_arguments: [described_class::GROUP_ID, 'up'], + interval: described_class::DELAY_INTERVAL + ) + end + end + + describe '#down' do + it 'deletes all batched migration records' do + migrate! + schema_migrate_down! + + expect(migration).not_to have_scheduled_batched_migration + end + end +end diff --git a/spec/migrations/populate_audit_event_streaming_verification_token_spec.rb b/spec/migrations/populate_audit_event_streaming_verification_token_spec.rb new file mode 100644 index 00000000000..b3fe1776183 --- /dev/null +++ b/spec/migrations/populate_audit_event_streaming_verification_token_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe PopulateAuditEventStreamingVerificationToken do + let(:groups) { table(:namespaces) } + let(:destinations) { table(:audit_events_external_audit_event_destinations) } + let(:migration) { described_class.new } + + let!(:group) { groups.create!(name: 'test-group', path: 'test-group') } + let!(:destination) { destinations.create!(namespace_id: group.id, destination_url: 'https://example.com/destination', verification_token: nil) } + + describe '#up' do + it 'adds verification tokens to records created before the migration' do + expect do + migrate! + destination.reload + end.to change { destination.verification_token }.from(nil).to(a_string_matching(/\w{24}/)) + end + end +end diff --git a/spec/migrations/schedule_fix_incorrect_max_seats_used2_spec.rb b/spec/migrations/schedule_fix_incorrect_max_seats_used2_spec.rb new file mode 100644 index 00000000000..3720be6cf3e --- /dev/null +++ b/spec/migrations/schedule_fix_incorrect_max_seats_used2_spec.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe ScheduleFixIncorrectMaxSeatsUsed2, :migration do + let(:migration_name) { described_class::MIGRATION.to_s.demodulize } + + describe '#up' do + it 'schedules a job on Gitlab.com' do + allow(Gitlab).to receive(:com?).and_return(true) + + Sidekiq::Testing.fake! do + freeze_time do + migrate! + + expect(migration_name).to be_scheduled_delayed_migration(1.hour, 'batch_2_for_start_date_before_02_aug_2021') + expect(BackgroundMigrationWorker.jobs.size).to eq(1) + end + end + end + + it 'does not schedule any jobs when not Gitlab.com' do + allow(Gitlab).to receive(:com?).and_return(false) + + Sidekiq::Testing.fake! do + migrate! + + expect(migration_name).not_to be_scheduled_delayed_migration + expect(BackgroundMigrationWorker.jobs.size).to eq(0) + end + end + end +end diff --git a/spec/migrations/schedule_fix_incorrect_max_seats_used_spec.rb b/spec/migrations/schedule_fix_incorrect_max_seats_used_spec.rb new file mode 100644 index 00000000000..74258f03630 --- /dev/null +++ b/spec/migrations/schedule_fix_incorrect_max_seats_used_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe ScheduleFixIncorrectMaxSeatsUsed, :migration do + let(:migration) { described_class.new } + + describe '#up' do + it 'schedules a job on Gitlab.com' do + allow(Gitlab).to receive(:com?).and_return(true) + + expect(migration).to receive(:migrate_in).with(1.hour, 'FixIncorrectMaxSeatsUsed') + + migration.up + end + + it 'does not schedule any jobs when not Gitlab.com' do + allow(Gitlab::CurrentSettings).to receive(:com?).and_return(false) + + expect(migration).not_to receive(:migrate_in) + + migration.up + end + end +end diff --git a/spec/migrations/schedule_recalculate_vulnerability_finding_signatures_for_findings_spec.rb b/spec/migrations/schedule_recalculate_vulnerability_finding_signatures_for_findings_spec.rb index 2545bb4a66c..9b62dd79e08 100644 --- a/spec/migrations/schedule_recalculate_vulnerability_finding_signatures_for_findings_spec.rb +++ b/spec/migrations/schedule_recalculate_vulnerability_finding_signatures_for_findings_spec.rb @@ -51,16 +51,17 @@ RSpec.describe ScheduleRecalculateVulnerabilityFindingSignaturesForFindings, :mi let_it_be(:finding3) { findings.create!(finding_params) } let_it_be(:signature3) { vulnerability_finding_signatures.create!(finding_id: finding3.id, algorithm_type: 0, signature_sha: ::Digest::SHA1.digest(SecureRandom.hex(50))) } - it 'schedules the background jobs', :aggregate_failure do + # this migration is now a no-op + it 'does not schedule the background jobs', :aggregate_failure do Sidekiq::Testing.fake! do freeze_time do migrate! - expect(BackgroundMigrationWorker.jobs.size).to eq(2) + expect(BackgroundMigrationWorker.jobs.size).to eq(0) expect(described_class::MIGRATION) - .to be_scheduled_migration_with_multiple_args(signature1.id, signature2.id) + .not_to be_scheduled_migration_with_multiple_args(signature1.id, signature2.id) expect(described_class::MIGRATION) - .to be_scheduled_migration_with_multiple_args(signature3.id, signature3.id) + .not_to be_scheduled_migration_with_multiple_args(signature3.id, signature3.id) end end end diff --git a/spec/migrations/start_backfill_ci_queuing_tables_spec.rb b/spec/migrations/start_backfill_ci_queuing_tables_spec.rb new file mode 100644 index 00000000000..a1e4179efb6 --- /dev/null +++ b/spec/migrations/start_backfill_ci_queuing_tables_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe StartBackfillCiQueuingTables do + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + let(:builds) { table(:ci_builds) } + + let!(:namespace) do + namespaces.create!(name: 'namespace1', path: 'namespace1') + end + + let!(:project) do + projects.create!(namespace_id: namespace.id, name: 'test1', path: 'test1') + end + + let!(:pending_build_1) do + builds.create!(status: :pending, name: 'test1', type: 'Ci::Build', project_id: project.id) + end + + let!(:running_build) do + builds.create!(status: :running, name: 'test2', type: 'Ci::Build', project_id: project.id) + end + + let!(:pending_build_2) do + builds.create!(status: :pending, name: 'test3', type: 'Ci::Build', project_id: project.id) + end + + before do + stub_const("#{described_class.name}::BATCH_SIZE", 1) + end + + it 'schedules jobs for builds that are pending' do + Sidekiq::Testing.fake! do + freeze_time do + migrate! + + expect(described_class::MIGRATION).to be_scheduled_delayed_migration( + 2.minutes, pending_build_1.id, pending_build_1.id) + expect(described_class::MIGRATION).to be_scheduled_delayed_migration( + 4.minutes, pending_build_2.id, pending_build_2.id) + expect(BackgroundMigrationWorker.jobs.size).to eq(2) + end + end + end +end diff --git a/spec/migrations/update_default_scan_method_of_dast_site_profile_spec.rb b/spec/migrations/update_default_scan_method_of_dast_site_profile_spec.rb new file mode 100644 index 00000000000..b73aa7016a1 --- /dev/null +++ b/spec/migrations/update_default_scan_method_of_dast_site_profile_spec.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe UpdateDefaultScanMethodOfDastSiteProfile do + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + let(:dast_sites) { table(:dast_sites) } + let(:dast_site_profiles) { table(:dast_site_profiles) } + + before do + namespace = namespaces.create!(name: 'test', path: 'test') + project = projects.create!(id: 12, namespace_id: namespace.id, name: 'gitlab', path: 'gitlab') + dast_site = dast_sites.create!(id: 1, url: 'https://www.gitlab.com', project_id: project.id) + + dast_site_profiles.create!(id: 1, project_id: project.id, dast_site_id: dast_site.id, + name: "#{FFaker::Product.product_name.truncate(192)} #{SecureRandom.hex(4)} - 0", + scan_method: 0, target_type: 0) + + dast_site_profiles.create!(id: 2, project_id: project.id, dast_site_id: dast_site.id, + name: "#{FFaker::Product.product_name.truncate(192)} #{SecureRandom.hex(4)} - 1", + scan_method: 0, target_type: 1) + end + + it 'updates the scan_method to 1 for profiles with target_type 1' do + migrate! + + expect(dast_site_profiles.where(scan_method: 1).count).to eq 1 + expect(dast_site_profiles.where(scan_method: 0).count).to eq 1 + end +end |