diff options
Diffstat (limited to 'spec/lib/gitlab/bitbucket_import')
6 files changed, 422 insertions, 4 deletions
diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb index 4c94ecfe745..9786e7a364e 100644 --- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb @@ -92,6 +92,7 @@ RSpec.describe Gitlab::BitbucketImport::Importer, :clean_gitlab_redis_cache, fea describe '#import_pull_requests' do let(:source_branch_sha) { sample.commits.last } + let(:merge_commit_sha) { sample.commits.second } let(:target_branch_sha) { sample.commits.first } let(:pull_request) do instance_double( @@ -101,6 +102,7 @@ RSpec.describe Gitlab::BitbucketImport::Importer, :clean_gitlab_redis_cache, fea source_branch_name: Gitlab::Git::BRANCH_REF_PREFIX + sample.source_branch, target_branch_sha: target_branch_sha, target_branch_name: Gitlab::Git::BRANCH_REF_PREFIX + sample.target_branch, + merge_commit_sha: merge_commit_sha, title: 'This is a title', description: 'This is a test pull request', state: 'merged', @@ -217,17 +219,29 @@ RSpec.describe Gitlab::BitbucketImport::Importer, :clean_gitlab_redis_cache, fea end end - context "when branches' sha is not found in the repository" do + context 'when source SHA is not found in the repository' do let(:source_branch_sha) { 'a' * Commit::MIN_SHA_LENGTH } - let(:target_branch_sha) { 'b' * Commit::MIN_SHA_LENGTH } + let(:target_branch_sha) { 'c' * Commit::MIN_SHA_LENGTH } - it 'uses the pull request sha references' do + it 'uses merge commit SHA for source' do expect { subject.execute }.to change { MergeRequest.count }.by(1) merge_request_diff = MergeRequest.first.merge_request_diff - expect(merge_request_diff.head_commit_sha).to eq source_branch_sha + expect(merge_request_diff.head_commit_sha).to eq merge_commit_sha expect(merge_request_diff.start_commit_sha).to eq target_branch_sha end + + context 'when the merge commit SHA is also not found' do + let(:merge_commit_sha) { 'b' * Commit::MIN_SHA_LENGTH } + + it 'uses the pull request sha references' do + expect { subject.execute }.to change { MergeRequest.count }.by(1) + + merge_request_diff = MergeRequest.first.merge_request_diff + expect(merge_request_diff.head_commit_sha).to eq source_branch_sha + expect(merge_request_diff.start_commit_sha).to eq target_branch_sha + end + end end context "when target_branch_sha is blank" do diff --git a/spec/lib/gitlab/bitbucket_import/importers/pull_request_importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importers/pull_request_importer_spec.rb new file mode 100644 index 00000000000..2eca6bb47d6 --- /dev/null +++ b/spec/lib/gitlab/bitbucket_import/importers/pull_request_importer_spec.rb @@ -0,0 +1,166 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::BitbucketImport::Importers::PullRequestImporter, :clean_gitlab_redis_cache, feature_category: :importers do + include AfterNextHelpers + + let_it_be(:project) { create(:project, :repository) } + let_it_be(:bitbucket_user) { create(:user) } + let_it_be(:user_2) { create(:user) } + let_it_be(:user_3) { create(:user) } + let_it_be(:identity) { create(:identity, user: bitbucket_user, extern_uid: 'bitbucket_user', provider: :bitbucket) } + let_it_be(:identity_2) { create(:identity, user: user_2, extern_uid: 'user_2', provider: :bitbucket) } + let(:source_branch_sha) { project.repository.commit.sha } + let(:target_branch_sha) { project.repository.commit('refs/heads/master').sha } + + let(:hash) do + { + author: 'bitbucket_user', + created_at: Date.today, + description: 'description', + iid: 11, + source_branch_name: 'source-branch-name', + source_branch_sha: source_branch_sha, + state: 'merged', + target_branch_name: 'destination-branch-name', + target_branch_sha: target_branch_sha, + title: 'title', + updated_at: Date.today, + reviewers: %w[user_2 user_3] + } + end + + subject(:importer) { described_class.new(project, hash) } + + describe '#execute' do + it 'calls MergeRequestCreator' do + expect(Gitlab::Import::MergeRequestCreator).to receive_message_chain(:new, :execute) + + importer.execute + end + + it 'creates a merge request with the correct attributes' do + expect { importer.execute }.to change { project.merge_requests.count }.from(0).to(1) + + merge_request = project.merge_requests.first + + expect(merge_request.iid).to eq(11) + expect(merge_request.author).to eq(bitbucket_user) + expect(merge_request.title).to eq('title') + expect(merge_request.merged?).to be_truthy + expect(merge_request.created_at).to eq(Date.today) + expect(merge_request.description).to eq('description') + expect(merge_request.source_project_id).to eq(project.id) + expect(merge_request.target_project_id).to eq(project.id) + expect(merge_request.source_branch).to eq('source-branch-name') + expect(merge_request.target_branch).to eq('destination-branch-name') + expect(merge_request.assignee_ids).to eq([bitbucket_user.id]) + expect(merge_request.reviewer_ids).to eq([user_2.id]) + expect(merge_request.merge_request_diffs.first.base_commit_sha).to eq(source_branch_sha) + expect(merge_request.merge_request_diffs.first.head_commit_sha).to eq(target_branch_sha) + end + + context 'when the state is closed' do + it 'marks merge request as closed' do + described_class.new(project, hash.merge(state: 'closed')).execute + + expect(project.merge_requests.first.closed?).to be_truthy + end + end + + context 'when the state is opened' do + it 'marks merge request as opened' do + described_class.new(project, hash.merge(state: 'opened')).execute + + expect(project.merge_requests.first.opened?).to be_truthy + end + end + + context 'when the author does not have a bitbucket identity' do + before do + identity.update!(provider: :github) + end + + it 'sets the author and assignee to the project creator and adds the author to the description' do + importer.execute + + merge_request = project.merge_requests.first + + expect(merge_request.author).to eq(project.creator) + expect(merge_request.assignee).to eq(project.creator) + expect(merge_request.description).to eq("*Created by: bitbucket_user*\n\ndescription") + end + end + + context 'when none of the reviewers have an identity' do + before do + identity_2.destroy! + end + + it 'does not set reviewer_ids' do + importer.execute + + merge_request = project.merge_requests.first + + expect(merge_request.reviewer_ids).to be_empty + end + end + + describe 'head_commit_sha for merge request diff' do + let(:diff) { project.merge_requests.first.merge_request_diffs.first } + let(:min_length) { Commit::MIN_SHA_LENGTH } + + context 'when the source commit hash from Bitbucket is found on the repo' do + it 'is set to the source commit hash' do + described_class.new(project, hash.merge(source_branch_sha: source_branch_sha)).execute + + expect(diff.head_commit_sha).to eq(source_branch_sha) + end + end + + context 'when the source commit hash is not found but the merge commit hash is found' do + it 'is set to the merge commit hash' do + attrs = { source_branch_sha: 'x' * min_length, merge_commit_sha: source_branch_sha } + + described_class.new(project, hash.merge(attrs)).execute + + expect(diff.head_commit_sha).to eq(source_branch_sha) + end + end + + context 'when both the source commit and merge commit hash are not found' do + it 'is nil' do + attrs = { source_branch_sha: 'x' * min_length, merge_commit_sha: 'y' * min_length } + + described_class.new(project, hash.merge(attrs)).execute + + expect(diff.head_commit_sha).to be_nil + end + end + end + + context 'when an error is raised' do + before do + allow(Gitlab::Import::MergeRequestCreator).to receive(:new).and_raise(StandardError) + end + + it 'tracks the failure and does not fail' do + expect(Gitlab::Import::ImportFailureService).to receive(:track).once + + importer.execute + end + end + + it 'logs its progress' do + allow(Gitlab::Import::MergeRequestCreator).to receive_message_chain(:new, :execute) + + expect(Gitlab::BitbucketImport::Logger) + .to receive(:info).with(include(message: 'starting', iid: anything)).and_call_original + expect(Gitlab::BitbucketImport::Logger) + .to receive(:info).with(include(message: 'finished', iid: anything)).and_call_original + + importer.execute + end + end +end diff --git a/spec/lib/gitlab/bitbucket_import/importers/pull_requests_importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importers/pull_requests_importer_spec.rb new file mode 100644 index 00000000000..46bf099de0c --- /dev/null +++ b/spec/lib/gitlab/bitbucket_import/importers/pull_requests_importer_spec.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::BitbucketImport::Importers::PullRequestsImporter, feature_category: :importers do + let_it_be(:project) do + create(:project, :import_started, + import_data_attributes: { + data: { 'project_key' => 'key', 'repo_slug' => 'slug' }, + credentials: { 'base_uri' => 'http://bitbucket.org/', 'user' => 'bitbucket', 'password' => 'password' } + } + ) + end + + subject(:importer) { described_class.new(project) } + + describe '#execute', :clean_gitlab_redis_cache do + before do + allow_next_instance_of(Bitbucket::Client) do |client| + allow(client).to receive(:pull_requests).and_return( + [ + Bitbucket::Representation::PullRequest.new({ 'id' => 1, 'state' => 'OPENED' }), + Bitbucket::Representation::PullRequest.new({ 'id' => 2, 'state' => 'DECLINED' }), + Bitbucket::Representation::PullRequest.new({ 'id' => 3, 'state' => 'MERGED' }) + ], + [] + ) + end + end + + it 'imports each pull request in parallel', :aggregate_failures do + expect(Gitlab::BitbucketImport::ImportPullRequestWorker).to receive(:perform_in).exactly(3).times + + waiter = importer.execute + + expect(waiter).to be_an_instance_of(Gitlab::JobWaiter) + expect(waiter.jobs_remaining).to eq(3) + expect(Gitlab::Cache::Import::Caching.values_from_set(importer.already_enqueued_cache_key)) + .to match_array(%w[1 2 3]) + end + + context 'when the client raises an error' do + before do + allow_next_instance_of(Bitbucket::Client) do |client| + allow(client).to receive(:pull_requests).and_raise(StandardError) + end + end + + it 'tracks the failure and does not fail' do + expect(Gitlab::Import::ImportFailureService).to receive(:track).once + + importer.execute + end + end + + context 'when pull request was already enqueued' do + before do + Gitlab::Cache::Import::Caching.set_add(importer.already_enqueued_cache_key, 1) + end + + it 'does not schedule job for enqueued pull requests', :aggregate_failures do + expect(Gitlab::BitbucketImport::ImportPullRequestWorker).to receive(:perform_in).twice + + waiter = importer.execute + + expect(waiter).to be_an_instance_of(Gitlab::JobWaiter) + expect(waiter.jobs_remaining).to eq(3) + end + end + end +end diff --git a/spec/lib/gitlab/bitbucket_import/importers/repository_importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importers/repository_importer_spec.rb new file mode 100644 index 00000000000..1caf0b884c2 --- /dev/null +++ b/spec/lib/gitlab/bitbucket_import/importers/repository_importer_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::BitbucketImport::Importers::RepositoryImporter, feature_category: :importers do + let_it_be(:project) { create(:project, import_url: 'https://bitbucket.org/vim/vim.git') } + + subject(:importer) { described_class.new(project) } + + describe '#execute' do + context 'when repository is empty' do + it 'imports the repository' do + expect(project.repository).to receive(:import_repository).with(project.import_url) + expect(project.repository).to receive(:fetch_as_mirror).with(project.import_url, + refmap: ['+refs/pull-requests/*/to:refs/merge-requests/*/head']) + expect(project.last_repository_updated_at).to be_present + + importer.execute + end + end + + context 'when repository is not empty' do + before do + allow(project).to receive(:empty_repo?).and_return(false) + + project.last_repository_updated_at = 1.day.ago + end + + it 'does not import the repository' do + expect(project.repository).not_to receive(:import_repository) + + expect { importer.execute }.not_to change { project.last_repository_updated_at } + end + end + + context 'when a Git CommandError is raised and the repository exists' do + before do + allow(project.repository).to receive(:import_repository).and_raise(::Gitlab::Git::CommandError) + allow(project).to receive(:repository_exists?).and_return(true) + end + + it 'expires repository caches' do + expect(project.repository).to receive(:expire_content_cache) + + expect { importer.execute }.to raise_error(::Gitlab::Git::CommandError) + end + end + end +end diff --git a/spec/lib/gitlab/bitbucket_import/parallel_importer_spec.rb b/spec/lib/gitlab/bitbucket_import/parallel_importer_spec.rb new file mode 100644 index 00000000000..29919c43d23 --- /dev/null +++ b/spec/lib/gitlab/bitbucket_import/parallel_importer_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::BitbucketImport::ParallelImporter, feature_category: :importers do + subject { described_class } + + it { is_expected.to be_async } + + describe '.track_start_import' do + it 'tracks the start of import' do + project = build_stubbed(:project) + + expect_next_instance_of(Gitlab::Import::Metrics, :bitbucket_importer, project) do |metric| + expect(metric).to receive(:track_start_import) + end + + subject.track_start_import(project) + end + end + + describe '#execute', :clean_gitlab_redis_shared_state do + let_it_be(:project) { create(:project) } + let(:importer) { subject.new(project) } + + before do + create(:import_state, :started, project: project) + end + + it 'schedules the importing of the repository' do + expect(Gitlab::BitbucketImport::Stage::ImportRepositoryWorker) + .to receive_message_chain(:with_status, :perform_async).with(project.id) + + expect(importer.execute).to eq(true) + end + + it 'sets the JID in Redis' do + expect(Gitlab::Import::SetAsyncJid).to receive(:set_jid).with(project.import_state).and_call_original + + importer.execute + end + end +end diff --git a/spec/lib/gitlab/bitbucket_import/user_finder_spec.rb b/spec/lib/gitlab/bitbucket_import/user_finder_spec.rb new file mode 100644 index 00000000000..4ac4c2e4813 --- /dev/null +++ b/spec/lib/gitlab/bitbucket_import/user_finder_spec.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::BitbucketImport::UserFinder, :clean_gitlab_redis_cache, feature_category: :importers do + let_it_be(:user) { create(:user) } + let_it_be(:identity) { create(:identity, user: user, extern_uid: 'uid', provider: :bitbucket) } + let(:created_id) { 1 } + let(:project) { instance_double(Project, creator_id: created_id, id: 1) } + let(:author) { 'uid' } + let(:cache_key) { format(described_class::USER_ID_FOR_AUTHOR_CACHE_KEY, project_id: project.id, author: author) } + + subject(:user_finder) { described_class.new(project) } + + describe '#find_user_id' do + it 'returns the user id' do + expect(User).to receive(:by_provider_and_extern_uid).and_call_original.once + + expect(user_finder.find_user_id(author)).to eq(user.id) + expect(user_finder.find_user_id(author)).to eq(user.id) + end + + context 'when the id is cached' do + before do + Gitlab::Cache::Import::Caching.write(cache_key, user.id) + end + + it 'does not attempt to find the user' do + expect(User).not_to receive(:by_provider_and_extern_uid) + + expect(user_finder.find_user_id(author)).to eq(user.id) + end + end + + context 'when -1 is cached' do + before do + Gitlab::Cache::Import::Caching.write(cache_key, -1) + end + + it 'does not attempt to find the user and returns nil' do + expect(User).not_to receive(:by_provider_and_extern_uid) + + expect(user_finder.find_user_id(author)).to be_nil + end + end + + context 'when the user does not have a matching bitbucket identity' do + before do + identity.update!(provider: :github) + end + + it 'returns nil' do + expect(user_finder.find_user_id(author)).to be_nil + end + end + end + + describe '#gitlab_user_id' do + context 'when find_user_id returns a user' do + it 'returns the user id' do + expect(user_finder.gitlab_user_id(project, author)).to eq(user.id) + end + end + + context 'when find_user_id does not return a user' do + before do + allow(user_finder).to receive(:find_user_id).and_return(nil) + end + + it 'returns the project creator' do + expect(user_finder.gitlab_user_id(project, author)).to eq(created_id) + end + end + end +end |