diff options
Diffstat (limited to 'spec/lib/gitlab/bitbucket_import/importers')
8 files changed, 628 insertions, 0 deletions
diff --git a/spec/lib/gitlab/bitbucket_import/importers/issue_importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importers/issue_importer_spec.rb new file mode 100644 index 00000000000..8f79390d2d9 --- /dev/null +++ b/spec/lib/gitlab/bitbucket_import/importers/issue_importer_spec.rb @@ -0,0 +1,103 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::BitbucketImport::Importers::IssueImporter, :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(:identity) { create(:identity, user: bitbucket_user, extern_uid: 'bitbucket_user', provider: :bitbucket) } + let_it_be(:default_work_item_type) { create(:work_item_type) } + let_it_be(:label) { create(:label, project: project) } + + let(:hash) do + { + iid: 111, + title: 'title', + description: 'description', + state: 'closed', + author: 'bitbucket_user', + milestone: 'my milestone', + issue_type_id: default_work_item_type.id, + label_id: label.id, + created_at: Date.today, + updated_at: Date.today + } + end + + subject(:importer) { described_class.new(project, hash) } + + before do + allow(Gitlab::Git).to receive(:ref_name).and_return('refname') + end + + describe '#execute' do + it 'creates an issue' do + expect { importer.execute }.to change { project.issues.count }.from(0).to(1) + + issue = project.issues.first + + expect(issue.description).to eq('description') + expect(issue.author).to eq(bitbucket_user) + expect(issue.closed?).to be_truthy + expect(issue.milestone).to eq(project.milestones.first) + expect(issue.work_item_type).to eq(default_work_item_type) + expect(issue.labels).to eq([label]) + expect(issue.created_at).to eq(Date.today) + expect(issue.updated_at).to eq(Date.today) + end + + context 'when the author does not have a bitbucket identity' do + before do + identity.update!(provider: :github) + end + + it 'sets the author to the project creator and adds the author to the description' do + importer.execute + + issue = project.issues.first + + expect(issue.author).to eq(project.creator) + expect(issue.description).to eq("*Created by: bitbucket_user*\n\ndescription") + end + end + + context 'when a milestone with the same title exists' do + let_it_be(:milestone) { create(:milestone, project: project, title: 'my milestone') } + + it 'assigns the milestone and does not create a new milestone' do + expect { importer.execute }.not_to change { project.milestones.count } + + expect(project.issues.first.milestone).to eq(milestone) + end + end + + context 'when a milestone with the same title does not exist' do + it 'creates a new milestone and assigns it' do + expect { importer.execute }.to change { project.milestones.count }.from(0).to(1) + + expect(project.issues.first.milestone).to eq(project.milestones.first) + end + end + + context 'when an error is raised' do + it 'tracks the failure and does not fail' do + expect(Gitlab::Import::ImportFailureService).to receive(:track).once + + described_class.new(project, hash.except(:title)).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/issue_notes_importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importers/issue_notes_importer_spec.rb new file mode 100644 index 00000000000..1a2a43d6877 --- /dev/null +++ b/spec/lib/gitlab/bitbucket_import/importers/issue_notes_importer_spec.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::BitbucketImport::Importers::IssueNotesImporter, :clean_gitlab_redis_cache, feature_category: :importers do + let_it_be(:project) do + create(:project, :import_started, import_source: 'namespace/repo', + import_data_attributes: { + credentials: { 'base_uri' => 'http://bitbucket.org/', 'user' => 'bitbucket', 'password' => 'password' } + } + ) + end + + let_it_be(:bitbucket_user) { create(:user) } + let_it_be(:identity) { create(:identity, user: bitbucket_user, extern_uid: 'bitbucket_user', provider: :bitbucket) } + let_it_be(:issue) { create(:issue, project: project) } + let(:hash) { { iid: issue.iid } } + let(:note_body) { 'body' } + let(:client) { Bitbucket::Client.new({}) } + + subject(:importer) { described_class.new(project, hash) } + + describe '#execute' do + let(:issue_comments_response) do + [ + Bitbucket::Representation::Comment.new({ + 'user' => { 'nickname' => 'bitbucket_user' }, + 'content' => { 'raw' => note_body }, + 'created_on' => Date.today, + 'updated_on' => Date.today + }) + ] + end + + before do + allow(Bitbucket::Client).to receive(:new).and_return(client) + allow(client).to receive(:issue_comments).and_return(issue_comments_response) + end + + it 'creates a new note with the correct attributes' do + expect { importer.execute }.to change { issue.notes.count }.from(0).to(1) + + note = issue.notes.first + + expect(note.project).to eq(project) + expect(note.note).to eq(note_body) + expect(note.author).to eq(bitbucket_user) + expect(note.created_at).to eq(Date.today) + expect(note.updated_at).to eq(Date.today) + end + + context 'when the author does not have a bitbucket identity' do + before do + identity.update!(provider: :github) + end + + it 'sets the author to the project creator and adds the author to the note' do + importer.execute + + note = issue.notes.first + + expect(note.author).to eq(project.creator) + expect(note.note).to eq("*Created by: bitbucket_user*\n\nbody") + end + end + + it 'calls RefConverter to convert Bitbucket refs to Gitlab refs' do + expect(importer.instance_values['ref_converter']).to receive(:convert_note).once + + importer.execute + end + + context 'when an error is raised' do + before do + allow(client).to receive(:issue_comments).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 + end +end diff --git a/spec/lib/gitlab/bitbucket_import/importers/issues_importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importers/issues_importer_spec.rb new file mode 100644 index 00000000000..a361a9343dd --- /dev/null +++ b/spec/lib/gitlab/bitbucket_import/importers/issues_importer_spec.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::BitbucketImport::Importers::IssuesImporter, 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(:issues).and_return( + [ + Bitbucket::Representation::Issue.new({ 'id' => 1 }), + Bitbucket::Representation::Issue.new({ 'id' => 2 }) + ], + [] + ) + end + end + + it 'imports each issue in parallel', :aggregate_failures do + expect(Gitlab::BitbucketImport::ImportIssueWorker).to receive(:perform_in).twice + + waiter = importer.execute + + expect(waiter).to be_an_instance_of(Gitlab::JobWaiter) + expect(waiter.jobs_remaining).to eq(2) + expect(Gitlab::Cache::Import::Caching.values_from_set(importer.already_enqueued_cache_key)) + .to match_array(%w[1 2]) + end + + context 'when the client raises an error' do + before do + allow_next_instance_of(Bitbucket::Client) do |client| + allow(client).to receive(:issues).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 issue 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 issues', :aggregate_failures do + expect(Gitlab::BitbucketImport::ImportIssueWorker).to receive(:perform_in).once + + waiter = importer.execute + + expect(waiter).to be_an_instance_of(Gitlab::JobWaiter) + expect(waiter.jobs_remaining).to eq(2) + end + end + end +end diff --git a/spec/lib/gitlab/bitbucket_import/importers/issues_notes_importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importers/issues_notes_importer_spec.rb new file mode 100644 index 00000000000..043cd7f17b9 --- /dev/null +++ b/spec/lib/gitlab/bitbucket_import/importers/issues_notes_importer_spec.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::BitbucketImport::Importers::IssuesNotesImporter, feature_category: :importers do + let_it_be(:project) { create(:project, :import_started) } + # let_it_be(:merge_request_1) { create(:merge_request, source_project: project) } + # let_it_be(:merge_request_2) { create(:merge_request, source_project: project, source_branch: 'other-branch') } + let_it_be(:issue_1) { create(:issue, project: project) } + let_it_be(:issue_2) { create(:issue, project: project) } + + subject(:importer) { described_class.new(project) } + + describe '#execute', :clean_gitlab_redis_cache do + it 'imports the notes from each issue in parallel', :aggregate_failures do + expect(Gitlab::BitbucketImport::ImportIssueNotesWorker).to receive(:perform_in).twice + + waiter = importer.execute + + expect(waiter).to be_an_instance_of(Gitlab::JobWaiter) + expect(waiter.jobs_remaining).to eq(2) + expect(Gitlab::Cache::Import::Caching.values_from_set(importer.already_enqueued_cache_key)) + .to match_array(%w[1 2]) + end + + context 'when an error is raised' do + before do + allow(importer).to receive(:mark_as_enqueued).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 + + context 'when issue was already enqueued' do + before do + Gitlab::Cache::Import::Caching.set_add(importer.already_enqueued_cache_key, 2) + end + + it 'does not schedule job for enqueued issues', :aggregate_failures do + expect(Gitlab::BitbucketImport::ImportIssueNotesWorker).to receive(:perform_in).once + + waiter = importer.execute + + expect(waiter).to be_an_instance_of(Gitlab::JobWaiter) + expect(waiter.jobs_remaining).to eq(2) + end + end + end +end diff --git a/spec/lib/gitlab/bitbucket_import/importers/lfs_object_importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importers/lfs_object_importer_spec.rb new file mode 100644 index 00000000000..4d56853032a --- /dev/null +++ b/spec/lib/gitlab/bitbucket_import/importers/lfs_object_importer_spec.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::BitbucketImport::Importers::LfsObjectImporter, feature_category: :importers do + let_it_be(:project) { create(:project) } + let(:oid) { 'a' * 64 } + + let(:lfs_attributes) do + { + 'oid' => oid, + 'size' => 1, + 'link' => 'http://www.gitlab.com/lfs_objects/oid', + 'headers' => { 'X-Some-Header' => '456' } + } + end + + let(:importer) { described_class.new(project, lfs_attributes) } + + describe '#execute' do + it 'calls the LfsDownloadService with the lfs object attributes' do + expect_next_instance_of( + Projects::LfsPointers::LfsDownloadService, project, have_attributes(lfs_attributes) + ) do |service| + expect(service).to receive(:execute).and_return(ServiceResponse.success) + end + + importer.execute + end + + context 'when the object is not valid' do + let(:oid) { 'invalid' } + + it 'tracks the validation errors and does not continue' do + expect(Gitlab::Import::ImportFailureService).to receive(:track).once + + expect(Projects::LfsPointers::LfsDownloadService).not_to receive(:new) + + importer.execute + end + end + + context 'when an error is raised' do + let(:exception) { StandardError.new('messsage') } + + before do + allow_next_instance_of(Projects::LfsPointers::LfsDownloadService) do |service| + allow(service).to receive(:execute).and_raise(exception) + end + end + + it 'rescues and logs the exception' do + expect(Gitlab::Import::ImportFailureService) + .to receive(:track) + .with(hash_including(exception: exception)) + + importer.execute + end + end + + it 'logs its progress' do + allow_next_instance_of(Projects::LfsPointers::LfsDownloadService) do |service| + allow(service).to receive(:execute).and_return(ServiceResponse.success) + end + + common_log_message = { + oid: oid, + import_stage: 'import_lfs_object', + class: described_class.name, + project_id: project.id, + project_path: project.full_path + } + + expect(Gitlab::BitbucketImport::Logger) + .to receive(:info).with(common_log_message.merge(message: 'starting')).and_call_original + expect(Gitlab::BitbucketImport::Logger) + .to receive(:info).with(common_log_message.merge(message: 'finished')).and_call_original + + importer.execute + end + end +end diff --git a/spec/lib/gitlab/bitbucket_import/importers/lfs_objects_importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importers/lfs_objects_importer_spec.rb new file mode 100644 index 00000000000..fbce8337264 --- /dev/null +++ b/spec/lib/gitlab/bitbucket_import/importers/lfs_objects_importer_spec.rb @@ -0,0 +1,116 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::BitbucketImport::Importers::LfsObjectsImporter, feature_category: :importers do + let_it_be(:project) do + create(:project, :import_started, + import_data_attributes: { + data: { 'project_key' => 'key', 'repo_slug' => 'slug' }, + credentials: { 'token' => 'token' } + } + ) + end + + let(:lfs_attributes) do + { + oid: 'a' * 64, + size: 1, + link: 'http://www.gitlab.com/lfs_objects/oid', + headers: { 'X-Some-Header' => '456' } + } + end + + let(:lfs_download_object) { LfsDownloadObject.new(**lfs_attributes) } + + let(:common_log_messages) do + { + import_stage: 'import_lfs_objects', + class: described_class.name, + project_id: project.id, + project_path: project.full_path + } + end + + describe '#execute', :clean_gitlab_redis_cache do + context 'when lfs is enabled' do + before do + allow(project).to receive(:lfs_enabled?).and_return(true) + end + + it 'imports each lfs object in parallel' do + importer = described_class.new(project) + + expect_next_instance_of(Projects::LfsPointers::LfsObjectDownloadListService) do |service| + expect(service).to receive(:each_list_item).and_yield(lfs_download_object) + end + + expect(Gitlab::BitbucketImport::ImportLfsObjectWorker).to receive(:perform_in) + .with(1.second, project.id, lfs_attributes.stringify_keys, start_with(Gitlab::JobWaiter::KEY_PREFIX)) + + waiter = importer.execute + + expect(waiter).to be_an_instance_of(Gitlab::JobWaiter) + expect(waiter.jobs_remaining).to eq(1) + end + + it 'logs its progress' do + importer = described_class.new(project) + + expect(Gitlab::BitbucketImport::Logger) + .to receive(:info).with(common_log_messages.merge(message: 'starting')).and_call_original + expect(Gitlab::BitbucketImport::Logger) + .to receive(:info).with(common_log_messages.merge(message: 'finished')).and_call_original + + importer.execute + end + + context 'when LFS list download fails' do + let(:exception) { StandardError.new('Invalid Project URL') } + + before do + allow_next_instance_of(Projects::LfsPointers::LfsObjectDownloadListService) do |service| + allow(service).to receive(:each_list_item).and_raise(exception) + end + end + + it 'rescues and logs the exception' do + importer = described_class.new(project) + + expect(Gitlab::Import::ImportFailureService) + .to receive(:track) + .with( + project_id: project.id, + exception: exception, + error_source: described_class.name + ).and_call_original + + expect(Gitlab::BitbucketImport::ImportLfsObjectWorker).not_to receive(:perform_in) + + waiter = importer.execute + + expect(waiter).to be_an_instance_of(Gitlab::JobWaiter) + expect(waiter.jobs_remaining).to eq(0) + end + end + end + + context 'when LFS is not enabled' do + before do + allow(project).to receive(:lfs_enabled?).and_return(false) + end + + it 'logs progress but does nothing' do + importer = described_class.new(project) + + expect(Gitlab::BitbucketImport::Logger).to receive(:info).twice + expect(Gitlab::BitbucketImport::ImportLfsObjectWorker).not_to receive(:perform_in) + + waiter = importer.execute + + expect(waiter).to be_an_instance_of(Gitlab::JobWaiter) + expect(waiter.jobs_remaining).to eq(0) + end + end + end +end diff --git a/spec/lib/gitlab/bitbucket_import/importers/pull_request_notes_importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importers/pull_request_notes_importer_spec.rb new file mode 100644 index 00000000000..4a30f225d66 --- /dev/null +++ b/spec/lib/gitlab/bitbucket_import/importers/pull_request_notes_importer_spec.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::BitbucketImport::Importers::PullRequestNotesImporter, feature_category: :importers do + let_it_be(:project) do + create(:project, :import_started, + import_data_attributes: { + credentials: { 'base_uri' => 'http://bitbucket.org/', 'user' => 'bitbucket', 'password' => 'password' } + } + ) + end + + let_it_be(:merge_request) { create(:merge_request, source_project: project) } + + let(:hash) { { iid: merge_request.iid } } + let(:importer_helper) { Gitlab::BitbucketImport::Importer.new(project) } + + subject(:importer) { described_class.new(project, hash) } + + before do + allow(Gitlab::BitbucketImport::Importer).to receive(:new).and_return(importer_helper) + end + + describe '#execute' do + it 'calls Importer.import_pull_request_comments' do + expect(importer_helper).to receive(:import_pull_request_comments).once + + importer.execute + end + + context 'when the merge request does not exist' do + let(:hash) { { iid: 'nonexistent' } } + + it 'does not call Importer.import_pull_request_comments' do + expect(importer_helper).not_to receive(:import_pull_request_comments) + + importer.execute + end + end + + context 'when the merge request exists but not for this project' do + let_it_be(:another_project) { create(:project) } + + before do + merge_request.update!(source_project: another_project, target_project: another_project) + end + + it 'does not call Importer.import_pull_request_comments' do + expect(importer_helper).not_to receive(:import_pull_request_comments) + + importer.execute + end + end + + context 'when an error is raised' do + before do + allow(importer_helper).to receive(:import_pull_request_comments).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 + end +end diff --git a/spec/lib/gitlab/bitbucket_import/importers/pull_requests_notes_importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importers/pull_requests_notes_importer_spec.rb new file mode 100644 index 00000000000..c44fc259c3b --- /dev/null +++ b/spec/lib/gitlab/bitbucket_import/importers/pull_requests_notes_importer_spec.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::BitbucketImport::Importers::PullRequestsNotesImporter, feature_category: :importers do + let_it_be(:project) { create(:project, :import_started) } + let_it_be(:merge_request_1) { create(:merge_request, source_project: project) } + let_it_be(:merge_request_2) { create(:merge_request, source_project: project, source_branch: 'other-branch') } + + subject(:importer) { described_class.new(project) } + + describe '#execute', :clean_gitlab_redis_cache do + it 'imports the notes from each merge request in parallel', :aggregate_failures do + expect(Gitlab::BitbucketImport::ImportPullRequestNotesWorker).to receive(:perform_in).twice + + waiter = importer.execute + + expect(waiter).to be_an_instance_of(Gitlab::JobWaiter) + expect(waiter.jobs_remaining).to eq(2) + expect(Gitlab::Cache::Import::Caching.values_from_set(importer.already_enqueued_cache_key)) + .to match_array(%w[1 2]) + end + + context 'when an error is raised' do + before do + allow(importer).to receive(:mark_as_enqueued).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 + + context 'when merge request was already enqueued' do + before do + Gitlab::Cache::Import::Caching.set_add(importer.already_enqueued_cache_key, 2) + end + + it 'does not schedule job for enqueued merge requests', :aggregate_failures do + expect(Gitlab::BitbucketImport::ImportPullRequestNotesWorker).to receive(:perform_in).once + + waiter = importer.execute + + expect(waiter).to be_an_instance_of(Gitlab::JobWaiter) + expect(waiter.jobs_remaining).to eq(2) + end + end + end +end |