# frozen_string_literal: true require 'spec_helper' RSpec.describe Gitlab::BackgroundMigration::MigrateMergeRequestDiffCommitUsers do let(:namespaces) { table(:namespaces) } let(:projects) { table(:projects) } let(:users) { table(:users) } let(:merge_requests) { table(:merge_requests) } let(:diffs) { table(:merge_request_diffs) } let(:commits) do table(:merge_request_diff_commits).tap do |t| t.extend(SuppressCompositePrimaryKeyWarning) end end let(:commit_users) { described_class::MergeRequestDiffCommitUser } let(:namespace) { namespaces.create!(name: 'foo', path: 'foo') } let(:project) { projects.create!(namespace_id: namespace.id) } let(:merge_request) do merge_requests.create!( source_branch: 'x', target_branch: 'master', target_project_id: project.id ) end let(:diff) { diffs.create!(merge_request_id: merge_request.id) } let(:migration) { described_class.new } describe 'MergeRequestDiffCommit' do describe '.each_row_to_migrate' do it 'yields the rows to migrate for a given range' do commit1 = commits.create!( merge_request_diff_id: diff.id, relative_order: 0, sha: Gitlab::Database::ShaAttribute.serialize('123abc'), author_name: 'bob', author_email: 'bob@example.com', committer_name: 'bob', committer_email: 'bob@example.com' ) commit2 = commits.create!( merge_request_diff_id: diff.id, relative_order: 1, sha: Gitlab::Database::ShaAttribute.serialize('123abc'), author_name: 'Alice', author_email: 'alice@example.com', committer_name: 'Alice', committer_email: 'alice@example.com' ) # We stub this constant to make sure we run at least two pagination # queries for getting the data. This way we can test if the pagination # is actually working properly. stub_const( 'Gitlab::BackgroundMigration::MigrateMergeRequestDiffCommitUsers::COMMIT_ROWS_PER_QUERY', 1 ) rows = [] described_class::MergeRequestDiffCommit.each_row_to_migrate(diff.id, diff.id + 1) do |row| rows << row end expect(rows.length).to eq(2) expect(rows[0].author_name).to eq(commit1.author_name) expect(rows[1].author_name).to eq(commit2.author_name) end end end describe 'MergeRequestDiffCommitUser' do describe '.union' do it 'produces a union of the given queries' do alice = commit_users.create!(name: 'Alice', email: 'alice@example.com') bob = commit_users.create!(name: 'Bob', email: 'bob@example.com') users = commit_users.union([ commit_users.where(name: 'Alice').to_sql, commit_users.where(name: 'Bob').to_sql ]) expect(users).to include(alice) expect(users).to include(bob) end end end describe '#perform' do it 'skips jobs that have already been completed' do Gitlab::Database::BackgroundMigrationJob.create!( class_name: 'MigrateMergeRequestDiffCommitUsers', arguments: [1, 10], status: :succeeded ) expect(migration).not_to receive(:get_data_to_update) migration.perform(1, 10) end it 'migrates the data in the range' do commits.create!( merge_request_diff_id: diff.id, relative_order: 0, sha: Gitlab::Database::ShaAttribute.serialize('123abc'), author_name: 'bob', author_email: 'bob@example.com', committer_name: 'bob', committer_email: 'bob@example.com' ) migration.perform(diff.id, diff.id + 1) bob = commit_users.find_by(name: 'bob') commit = commits.first expect(commit.commit_author_id).to eq(bob.id) expect(commit.committer_id).to eq(bob.id) end it 'treats empty names and Emails the same as NULL values' do commits.create!( merge_request_diff_id: diff.id, relative_order: 0, sha: Gitlab::Database::ShaAttribute.serialize('123abc'), author_name: 'bob', author_email: 'bob@example.com', committer_name: '', committer_email: '' ) migration.perform(diff.id, diff.id + 1) bob = commit_users.find_by(name: 'bob') commit = commits.first expect(commit.commit_author_id).to eq(bob.id) expect(commit.committer_id).to be_nil end it 'does not update rows without a committer and author' do commits.create!( merge_request_diff_id: diff.id, relative_order: 0, sha: Gitlab::Database::ShaAttribute.serialize('123abc') ) migration.perform(diff.id, diff.id + 1) commit = commits.first expect(commit_users.count).to eq(0) expect(commit.commit_author_id).to be_nil expect(commit.committer_id).to be_nil end it 'marks the background job as done' do Gitlab::Database::BackgroundMigrationJob.create!( class_name: 'MigrateMergeRequestDiffCommitUsers', arguments: [diff.id, diff.id + 1] ) migration.perform(diff.id, diff.id + 1) job = Gitlab::Database::BackgroundMigrationJob.first expect(job.status).to eq('succeeded') end end describe '#get_data_to_update' do it 'returns the users and commit rows to update' do commits.create!( merge_request_diff_id: diff.id, relative_order: 0, sha: Gitlab::Database::ShaAttribute.serialize('123abc'), author_name: 'bob' + ('a' * 510), author_email: 'bob@example.com', committer_name: 'bob' + ('a' * 510), committer_email: 'bob@example.com' ) commits.create!( merge_request_diff_id: diff.id, relative_order: 1, sha: Gitlab::Database::ShaAttribute.serialize('456abc'), author_name: 'alice', author_email: 'alice@example.com', committer_name: 'alice', committer_email: 'alice@example.com' ) users, to_update = migration.get_data_to_update(diff.id, diff.id + 1) bob_name = 'bob' + ('a' * 509) expect(users).to include(%w[alice alice@example.com]) expect(users).to include([bob_name, 'bob@example.com']) expect(to_update[[diff.id, 0]]) .to eq([[bob_name, 'bob@example.com'], [bob_name, 'bob@example.com']]) expect(to_update[[diff.id, 1]]) .to eq([%w[alice alice@example.com], %w[alice alice@example.com]]) end it 'does not include a user if both the name and Email are missing' do commits.create!( merge_request_diff_id: diff.id, relative_order: 0, sha: Gitlab::Database::ShaAttribute.serialize('123abc'), author_name: nil, author_email: nil, committer_name: 'bob', committer_email: 'bob@example.com' ) users, _ = migration.get_data_to_update(diff.id, diff.id + 1) expect(users).to eq([%w[bob bob@example.com]].to_set) end end describe '#get_user_rows_in_batches' do it 'retrieves all existing users' do alice = commit_users.create!(name: 'alice', email: 'alice@example.com') bob = commit_users.create!(name: 'bob', email: 'bob@example.com') users = [[alice.name, alice.email], [bob.name, bob.email]] mapping = {} migration.get_user_rows_in_batches(users, mapping) expect(mapping[%w[alice alice@example.com]]).to eq(alice) expect(mapping[%w[bob bob@example.com]]).to eq(bob) end end describe '#create_missing_users' do it 'creates merge request diff commit users that are missing' do alice = commit_users.create!(name: 'alice', email: 'alice@example.com') users = [%w[alice alice@example.com], %w[bob bob@example.com]] mapping = { %w[alice alice@example.com] => alice } migration.create_missing_users(users, mapping) expect(mapping[%w[alice alice@example.com]]).to eq(alice) expect(mapping[%w[bob bob@example.com]].name).to eq('bob') expect(mapping[%w[bob bob@example.com]].email).to eq('bob@example.com') end end describe '#update_commit_rows' do it 'updates the merge request diff commit rows' do to_update = { [42, 0] => [%w[alice alice@example.com], []] } user_mapping = { %w[alice alice@example.com] => double(:user, id: 1) } expect(migration) .to receive(:bulk_update_commit_rows) .with({ [42, 0] => [1, nil] }) migration.update_commit_rows(to_update, user_mapping) end end describe '#bulk_update_commit_rows' do context 'when there are no authors and committers' do it 'does not update any rows' do migration.bulk_update_commit_rows({ [1, 0] => [] }) expect(described_class::MergeRequestDiffCommit.connection) .not_to receive(:execute) end end context 'when there are only authors' do it 'only updates the author IDs' do author = commit_users.create!(name: 'Alice', email: 'alice@example.com') commit = commits.create!( merge_request_diff_id: diff.id, relative_order: 0, sha: Gitlab::Database::ShaAttribute.serialize('123abc') ) mapping = { [commit.merge_request_diff_id, commit.relative_order] => [author.id, nil] } migration.bulk_update_commit_rows(mapping) commit = commits.first expect(commit.commit_author_id).to eq(author.id) expect(commit.committer_id).to be_nil end end context 'when there are only committers' do it 'only updates the committer IDs' do committer = commit_users.create!(name: 'Alice', email: 'alice@example.com') commit = commits.create!( merge_request_diff_id: diff.id, relative_order: 0, sha: Gitlab::Database::ShaAttribute.serialize('123abc') ) mapping = { [commit.merge_request_diff_id, commit.relative_order] => [nil, committer.id] } migration.bulk_update_commit_rows(mapping) commit = commits.first expect(commit.committer_id).to eq(committer.id) expect(commit.commit_author_id).to be_nil end end context 'when there are both authors and committers' do it 'updates both the author and committer IDs' do author = commit_users.create!(name: 'Bob', email: 'bob@example.com') committer = commit_users.create!(name: 'Alice', email: 'alice@example.com') commit = commits.create!( merge_request_diff_id: diff.id, relative_order: 0, sha: Gitlab::Database::ShaAttribute.serialize('123abc') ) mapping = { [commit.merge_request_diff_id, commit.relative_order] => [author.id, committer.id] } migration.bulk_update_commit_rows(mapping) commit = commits.first expect(commit.commit_author_id).to eq(author.id) expect(commit.committer_id).to eq(committer.id) end end context 'when there are multiple commit rows to update' do it 'updates all the rows' do author = commit_users.create!(name: 'Bob', email: 'bob@example.com') committer = commit_users.create!(name: 'Alice', email: 'alice@example.com') commit1 = commits.create!( merge_request_diff_id: diff.id, relative_order: 0, sha: Gitlab::Database::ShaAttribute.serialize('123abc') ) commit2 = commits.create!( merge_request_diff_id: diff.id, relative_order: 1, sha: Gitlab::Database::ShaAttribute.serialize('456abc') ) mapping = { [commit1.merge_request_diff_id, commit1.relative_order] => [author.id, committer.id], [commit2.merge_request_diff_id, commit2.relative_order] => [author.id, nil] } migration.bulk_update_commit_rows(mapping) commit1 = commits.find_by(relative_order: 0) commit2 = commits.find_by(relative_order: 1) expect(commit1.commit_author_id).to eq(author.id) expect(commit1.committer_id).to eq(committer.id) expect(commit2.commit_author_id).to eq(author.id) expect(commit2.committer_id).to be_nil end end end describe '#primary_key' do it 'returns the primary key for the commits table' do key = migration.primary_key expect(key.to_sql).to eq('("merge_request_diff_commits"."merge_request_diff_id", "merge_request_diff_commits"."relative_order")') end end describe '#prepare' do it 'trims a value to at most 512 characters' do expect(migration.prepare('€' * 1_000)).to eq('€' * 512) end it 'returns nil if the value is an empty string' do expect(migration.prepare('')).to be_nil end end end