Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-11-19 11:27:35 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-11-19 11:27:35 +0300
commit7e9c479f7de77702622631cff2628a9c8dcbc627 (patch)
treec8f718a08e110ad7e1894510980d2155a6549197 /spec/lib/bulk_imports
parente852b0ae16db4052c1c567d9efa4facc81146e88 (diff)
Add latest changes from gitlab-org/gitlab@13-6-stable-eev13.6.0-rc42
Diffstat (limited to 'spec/lib/bulk_imports')
-rw-r--r--spec/lib/bulk_imports/clients/http_spec.rb132
-rw-r--r--spec/lib/bulk_imports/common/extractors/graphql_extractor_spec.rb75
-rw-r--r--spec/lib/bulk_imports/common/loaders/entity_loader_spec.rb30
-rw-r--r--spec/lib/bulk_imports/common/transformers/graphql_cleaner_transformer_spec.rb88
-rw-r--r--spec/lib/bulk_imports/common/transformers/underscorify_keys_transformer_spec.rb27
-rw-r--r--spec/lib/bulk_imports/groups/loaders/group_loader_spec.rb81
-rw-r--r--spec/lib/bulk_imports/groups/pipelines/group_pipeline_spec.rb102
-rw-r--r--spec/lib/bulk_imports/groups/pipelines/subgroup_entities_pipeline_spec.rb82
-rw-r--r--spec/lib/bulk_imports/groups/transformers/group_attributes_transformer_spec.rb105
-rw-r--r--spec/lib/bulk_imports/groups/transformers/subgroup_to_entity_transformer_spec.rb25
-rw-r--r--spec/lib/bulk_imports/importers/group_importer_spec.rb56
-rw-r--r--spec/lib/bulk_imports/importers/groups_importer_spec.rb36
-rw-r--r--spec/lib/bulk_imports/pipeline/attributes_spec.rb57
-rw-r--r--spec/lib/bulk_imports/pipeline/context_spec.rb27
-rw-r--r--spec/lib/bulk_imports/pipeline/runner_spec.rb74
15 files changed, 997 insertions, 0 deletions
diff --git a/spec/lib/bulk_imports/clients/http_spec.rb b/spec/lib/bulk_imports/clients/http_spec.rb
new file mode 100644
index 00000000000..2d841b7fac2
--- /dev/null
+++ b/spec/lib/bulk_imports/clients/http_spec.rb
@@ -0,0 +1,132 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe BulkImports::Clients::Http do
+ include ImportSpecHelper
+
+ let(:uri) { 'http://gitlab.example' }
+ let(:token) { 'token' }
+ let(:resource) { 'resource' }
+
+ subject { described_class.new(uri: uri, token: token) }
+
+ describe '#get' do
+ let(:response_double) { double(code: 200, success?: true, parsed_response: {}) }
+
+ shared_examples 'performs network request' do
+ it 'performs network request' do
+ expect(Gitlab::HTTP).to receive(:get).with(*expected_args).and_return(response_double)
+
+ subject.get(resource)
+ end
+ end
+
+ describe 'request query' do
+ include_examples 'performs network request' do
+ let(:expected_args) do
+ [
+ anything,
+ hash_including(
+ query: {
+ page: described_class::DEFAULT_PAGE,
+ per_page: described_class::DEFAULT_PER_PAGE
+ }
+ )
+ ]
+ end
+ end
+ end
+
+ describe 'request headers' do
+ include_examples 'performs network request' do
+ let(:expected_args) do
+ [
+ anything,
+ hash_including(
+ headers: {
+ 'Content-Type' => 'application/json',
+ 'Authorization' => "Bearer #{token}"
+ }
+ )
+ ]
+ end
+ end
+ end
+
+ describe 'request uri' do
+ include_examples 'performs network request' do
+ let(:expected_args) do
+ ['http://gitlab.example:80/api/v4/resource', anything]
+ end
+ end
+ end
+
+ context 'error handling' do
+ context 'when error occurred' do
+ it 'raises ConnectionError' do
+ allow(Gitlab::HTTP).to receive(:get).and_raise(Errno::ECONNREFUSED)
+
+ expect { subject.get(resource) }.to raise_exception(described_class::ConnectionError)
+ end
+ end
+
+ context 'when response is not success' do
+ it 'raises ConnectionError' do
+ response_double = double(code: 503, success?: false)
+
+ allow(Gitlab::HTTP).to receive(:get).and_return(response_double)
+
+ expect { subject.get(resource) }.to raise_exception(described_class::ConnectionError)
+ end
+ end
+ end
+
+ describe '#each_page' do
+ let(:objects1) { [{ object: 1 }, { object: 2 }] }
+ let(:objects2) { [{ object: 3 }, { object: 4 }] }
+ let(:response1) { double(success?: true, headers: { 'x-next-page' => 2 }, parsed_response: objects1) }
+ let(:response2) { double(success?: true, headers: {}, parsed_response: objects2) }
+
+ before do
+ stub_http_get('groups', { page: 1, per_page: 30 }, response1)
+ stub_http_get('groups', { page: 2, per_page: 30 }, response2)
+ end
+
+ context 'with a block' do
+ it 'yields every retrieved page to the supplied block' do
+ pages = []
+
+ subject.each_page(:get, 'groups') { |page| pages << page }
+
+ expect(pages[0]).to be_an_instance_of(Array)
+ expect(pages[1]).to be_an_instance_of(Array)
+
+ expect(pages[0]).to eq(objects1)
+ expect(pages[1]).to eq(objects2)
+ end
+ end
+
+ context 'without a block' do
+ it 'returns an Enumerator' do
+ expect(subject.each_page(:get, :foo)).to be_an_instance_of(Enumerator)
+ end
+ end
+
+ private
+
+ def stub_http_get(path, query, response)
+ uri = "http://gitlab.example:80/api/v4/#{path}"
+ params = {
+ follow_redirects: false,
+ headers: {
+ "Authorization" => "Bearer token",
+ "Content-Type" => "application/json"
+ }
+ }.merge(query: query)
+
+ allow(Gitlab::HTTP).to receive(:get).with(uri, params).and_return(response)
+ end
+ end
+ end
+end
diff --git a/spec/lib/bulk_imports/common/extractors/graphql_extractor_spec.rb b/spec/lib/bulk_imports/common/extractors/graphql_extractor_spec.rb
new file mode 100644
index 00000000000..cde8e2d5c18
--- /dev/null
+++ b/spec/lib/bulk_imports/common/extractors/graphql_extractor_spec.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe BulkImports::Common::Extractors::GraphqlExtractor do
+ let(:graphql_client) { instance_double(BulkImports::Clients::Graphql) }
+ let(:import_entity) { create(:bulk_import_entity) }
+ let(:response) { double(original_hash: { foo: :bar }) }
+ let(:query) { { query: double(to_s: 'test', variables: {}) } }
+ let(:context) do
+ instance_double(
+ BulkImports::Pipeline::Context,
+ entity: import_entity
+ )
+ end
+
+ subject { described_class.new(query) }
+
+ before do
+ allow(subject).to receive(:graphql_client).and_return(graphql_client)
+ allow(graphql_client).to receive(:parse)
+ end
+
+ describe '#extract' do
+ before do
+ allow(subject).to receive(:query_variables).and_return({})
+ allow(graphql_client).to receive(:execute).and_return(response)
+ end
+
+ it 'returns an enumerator with fetched results' do
+ response = subject.extract(context)
+
+ expect(response).to be_instance_of(Enumerator)
+ expect(response.first).to eq({ foo: :bar })
+ end
+ end
+
+ describe 'query variables' do
+ before do
+ allow(graphql_client).to receive(:execute).and_return(response)
+ end
+
+ context 'when variables are present' do
+ let(:query) { { query: double(to_s: 'test', variables: { full_path: :source_full_path }) } }
+
+ it 'builds graphql query variables for import entity' do
+ expected_variables = { full_path: import_entity.source_full_path }
+
+ expect(graphql_client).to receive(:execute).with(anything, expected_variables)
+
+ subject.extract(context).first
+ end
+ end
+
+ context 'when no variables are present' do
+ let(:query) { { query: double(to_s: 'test', variables: nil) } }
+
+ it 'returns empty hash' do
+ expect(graphql_client).to receive(:execute).with(anything, nil)
+
+ subject.extract(context).first
+ end
+ end
+
+ context 'when variables are empty hash' do
+ let(:query) { { query: double(to_s: 'test', variables: {}) } }
+
+ it 'makes graphql request with empty hash' do
+ expect(graphql_client).to receive(:execute).with(anything, {})
+
+ subject.extract(context).first
+ end
+ end
+ end
+end
diff --git a/spec/lib/bulk_imports/common/loaders/entity_loader_spec.rb b/spec/lib/bulk_imports/common/loaders/entity_loader_spec.rb
new file mode 100644
index 00000000000..4de7d95172f
--- /dev/null
+++ b/spec/lib/bulk_imports/common/loaders/entity_loader_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe BulkImports::Common::Loaders::EntityLoader do
+ describe '#load' do
+ it "creates entities for the given data" do
+ group = create(:group, path: "imported-group")
+ parent_entity = create(:bulk_import_entity, group: group, bulk_import: create(:bulk_import))
+ context = instance_double(BulkImports::Pipeline::Context, entity: parent_entity)
+
+ data = {
+ source_type: :group_entity,
+ source_full_path: "parent/subgroup",
+ destination_name: "subgroup",
+ destination_namespace: parent_entity.group.full_path,
+ parent_id: parent_entity.id
+ }
+
+ expect { subject.load(context, data) }.to change(BulkImports::Entity, :count).by(1)
+
+ subgroup_entity = BulkImports::Entity.last
+
+ expect(subgroup_entity.source_full_path).to eq 'parent/subgroup'
+ expect(subgroup_entity.destination_namespace).to eq 'imported-group'
+ expect(subgroup_entity.destination_name).to eq 'subgroup'
+ expect(subgroup_entity.parent_id).to eq parent_entity.id
+ end
+ end
+end
diff --git a/spec/lib/bulk_imports/common/transformers/graphql_cleaner_transformer_spec.rb b/spec/lib/bulk_imports/common/transformers/graphql_cleaner_transformer_spec.rb
new file mode 100644
index 00000000000..8f39b6e7c93
--- /dev/null
+++ b/spec/lib/bulk_imports/common/transformers/graphql_cleaner_transformer_spec.rb
@@ -0,0 +1,88 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe BulkImports::Common::Transformers::GraphqlCleanerTransformer do
+ describe '#transform' do
+ let_it_be(:expected_output) do
+ {
+ 'name' => 'test',
+ 'fullName' => 'test',
+ 'description' => 'test',
+ 'labels' => [
+ { 'title' => 'label1' },
+ { 'title' => 'label2' },
+ { 'title' => 'label3' }
+ ]
+ }
+ end
+
+ it 'deep cleans hash from GraphQL keys' do
+ data = {
+ 'data' => {
+ 'group' => {
+ 'name' => 'test',
+ 'fullName' => 'test',
+ 'description' => 'test',
+ 'labels' => {
+ 'edges' => [
+ { 'node' => { 'title' => 'label1' } },
+ { 'node' => { 'title' => 'label2' } },
+ { 'node' => { 'title' => 'label3' } }
+ ]
+ }
+ }
+ }
+ }
+
+ transformed_data = described_class.new.transform(nil, data)
+
+ expect(transformed_data).to eq(expected_output)
+ end
+
+ context 'when data does not have data/group nesting' do
+ it 'deep cleans hash from GraphQL keys' do
+ data = {
+ 'name' => 'test',
+ 'fullName' => 'test',
+ 'description' => 'test',
+ 'labels' => {
+ 'edges' => [
+ { 'node' => { 'title' => 'label1' } },
+ { 'node' => { 'title' => 'label2' } },
+ { 'node' => { 'title' => 'label3' } }
+ ]
+ }
+ }
+
+ transformed_data = described_class.new.transform(nil, data)
+
+ expect(transformed_data).to eq(expected_output)
+ end
+ end
+
+ context 'when data is not a hash' do
+ it 'does not perform transformation' do
+ data = 'test'
+
+ transformed_data = described_class.new.transform(nil, data)
+
+ expect(transformed_data).to eq(data)
+ end
+ end
+
+ context 'when nested data is not an array or hash' do
+ it 'only removes top level data/group keys' do
+ data = {
+ 'data' => {
+ 'group' => 'test'
+ }
+ }
+
+ transformed_data = described_class.new.transform(nil, data)
+
+ expect(transformed_data).to eq('test')
+ end
+ end
+ end
+end
diff --git a/spec/lib/bulk_imports/common/transformers/underscorify_keys_transformer_spec.rb b/spec/lib/bulk_imports/common/transformers/underscorify_keys_transformer_spec.rb
new file mode 100644
index 00000000000..cdffa750694
--- /dev/null
+++ b/spec/lib/bulk_imports/common/transformers/underscorify_keys_transformer_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe BulkImports::Common::Transformers::UnderscorifyKeysTransformer do
+ describe '#transform' do
+ it 'deep underscorifies hash keys' do
+ data = {
+ 'fullPath' => 'Foo',
+ 'snakeKeys' => {
+ 'snakeCaseKey' => 'Bar',
+ 'moreKeys' => {
+ 'anotherSnakeCaseKey' => 'Test'
+ }
+ }
+ }
+
+ transformed_data = described_class.new.transform(nil, data)
+
+ expect(transformed_data).to have_key('full_path')
+ expect(transformed_data).to have_key('snake_keys')
+ expect(transformed_data['snake_keys']).to have_key('snake_case_key')
+ expect(transformed_data['snake_keys']).to have_key('more_keys')
+ expect(transformed_data.dig('snake_keys', 'more_keys')).to have_key('another_snake_case_key')
+ end
+ end
+end
diff --git a/spec/lib/bulk_imports/groups/loaders/group_loader_spec.rb b/spec/lib/bulk_imports/groups/loaders/group_loader_spec.rb
new file mode 100644
index 00000000000..b14dfc615a9
--- /dev/null
+++ b/spec/lib/bulk_imports/groups/loaders/group_loader_spec.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe BulkImports::Groups::Loaders::GroupLoader do
+ describe '#load' do
+ let(:user) { create(:user) }
+ let(:data) { { foo: :bar } }
+ let(:service_double) { instance_double(::Groups::CreateService) }
+ let(:entity) { create(:bulk_import_entity) }
+ let(:context) do
+ instance_double(
+ BulkImports::Pipeline::Context,
+ entity: entity,
+ current_user: user
+ )
+ end
+
+ subject { described_class.new }
+
+ context 'when user can create group' do
+ shared_examples 'calls Group Create Service to create a new group' do
+ it 'calls Group Create Service to create a new group' do
+ expect(::Groups::CreateService).to receive(:new).with(context.current_user, data).and_return(service_double)
+ expect(service_double).to receive(:execute)
+ expect(entity).to receive(:update!)
+
+ subject.load(context, data)
+ end
+ end
+
+ context 'when there is no parent group' do
+ before do
+ allow(Ability).to receive(:allowed?).with(user, :create_group).and_return(true)
+ end
+
+ include_examples 'calls Group Create Service to create a new group'
+ end
+
+ context 'when there is parent group' do
+ let(:parent) { create(:group) }
+ let(:data) { { 'parent_id' => parent.id } }
+
+ before do
+ allow(Ability).to receive(:allowed?).with(user, :create_subgroup, parent).and_return(true)
+ end
+
+ include_examples 'calls Group Create Service to create a new group'
+ end
+ end
+
+ context 'when user cannot create group' do
+ shared_examples 'does not create new group' do
+ it 'does not create new group' do
+ expect(::Groups::CreateService).not_to receive(:new)
+
+ subject.load(context, data)
+ end
+ end
+
+ context 'when there is no parent group' do
+ before do
+ allow(Ability).to receive(:allowed?).with(user, :create_group).and_return(false)
+ end
+
+ include_examples 'does not create new group'
+ end
+
+ context 'when there is parent group' do
+ let(:parent) { create(:group) }
+ let(:data) { { 'parent_id' => parent.id } }
+
+ before do
+ allow(Ability).to receive(:allowed?).with(user, :create_subgroup, parent).and_return(false)
+ end
+
+ include_examples 'does not create new group'
+ end
+ end
+ end
+end
diff --git a/spec/lib/bulk_imports/groups/pipelines/group_pipeline_spec.rb b/spec/lib/bulk_imports/groups/pipelines/group_pipeline_spec.rb
new file mode 100644
index 00000000000..3949dd23b49
--- /dev/null
+++ b/spec/lib/bulk_imports/groups/pipelines/group_pipeline_spec.rb
@@ -0,0 +1,102 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe BulkImports::Groups::Pipelines::GroupPipeline do
+ describe '#run' do
+ let(:user) { create(:user) }
+ let(:parent) { create(:group) }
+ let(:entity) do
+ create(
+ :bulk_import_entity,
+ source_full_path: 'source/full/path',
+ destination_name: 'My Destination Group',
+ destination_namespace: parent.full_path
+ )
+ end
+
+ let(:context) do
+ BulkImports::Pipeline::Context.new(
+ current_user: user,
+ entity: entity
+ )
+ end
+
+ let(:group_data) do
+ {
+ 'data' => {
+ 'group' => {
+ 'name' => 'source_name',
+ 'fullPath' => 'source/full/path',
+ 'visibility' => 'private',
+ 'projectCreationLevel' => 'developer',
+ 'subgroupCreationLevel' => 'maintainer',
+ 'description' => 'Group Description',
+ 'emailsDisabled' => true,
+ 'lfsEnabled' => false,
+ 'mentionsDisabled' => true
+ }
+ }
+ }
+ end
+
+ subject { described_class.new }
+
+ before do
+ allow_next_instance_of(BulkImports::Common::Extractors::GraphqlExtractor) do |extractor|
+ allow(extractor).to receive(:extract).and_return([group_data])
+ end
+
+ parent.add_owner(user)
+ end
+
+ it 'imports new group into destination group' do
+ group_path = 'my-destination-group'
+
+ subject.run(context)
+
+ imported_group = Group.find_by_path(group_path)
+
+ expect(imported_group).not_to be_nil
+ expect(imported_group.parent).to eq(parent)
+ expect(imported_group.path).to eq(group_path)
+ expect(imported_group.description).to eq(group_data.dig('data', 'group', 'description'))
+ expect(imported_group.visibility).to eq(group_data.dig('data', 'group', 'visibility'))
+ expect(imported_group.project_creation_level).to eq(Gitlab::Access.project_creation_string_options[group_data.dig('data', 'group', 'projectCreationLevel')])
+ expect(imported_group.subgroup_creation_level).to eq(Gitlab::Access.subgroup_creation_string_options[group_data.dig('data', 'group', 'subgroupCreationLevel')])
+ expect(imported_group.lfs_enabled?).to eq(group_data.dig('data', 'group', 'lfsEnabled'))
+ expect(imported_group.emails_disabled?).to eq(group_data.dig('data', 'group', 'emailsDisabled'))
+ expect(imported_group.mentions_disabled?).to eq(group_data.dig('data', 'group', 'mentionsDisabled'))
+ end
+ end
+
+ describe 'pipeline parts' do
+ it { expect(described_class).to include_module(BulkImports::Pipeline) }
+ it { expect(described_class).to include_module(BulkImports::Pipeline::Attributes) }
+ it { expect(described_class).to include_module(BulkImports::Pipeline::Runner) }
+
+ it 'has extractors' do
+ expect(described_class.extractors)
+ .to contain_exactly(
+ {
+ klass: BulkImports::Common::Extractors::GraphqlExtractor,
+ options: {
+ query: BulkImports::Groups::Graphql::GetGroupQuery
+ }
+ }
+ )
+ end
+
+ it 'has transformers' do
+ expect(described_class.transformers)
+ .to contain_exactly(
+ { klass: BulkImports::Common::Transformers::GraphqlCleanerTransformer, options: nil },
+ { klass: BulkImports::Common::Transformers::UnderscorifyKeysTransformer, options: nil },
+ { klass: BulkImports::Groups::Transformers::GroupAttributesTransformer, options: nil })
+ end
+
+ it 'has loaders' do
+ expect(described_class.loaders).to contain_exactly({ klass: BulkImports::Groups::Loaders::GroupLoader, options: nil })
+ end
+ end
+end
diff --git a/spec/lib/bulk_imports/groups/pipelines/subgroup_entities_pipeline_spec.rb b/spec/lib/bulk_imports/groups/pipelines/subgroup_entities_pipeline_spec.rb
new file mode 100644
index 00000000000..60a4a796682
--- /dev/null
+++ b/spec/lib/bulk_imports/groups/pipelines/subgroup_entities_pipeline_spec.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe BulkImports::Groups::Pipelines::SubgroupEntitiesPipeline do
+ describe '#run' do
+ let_it_be(:user) { create(:user) }
+ let(:parent) { create(:group, name: 'imported-group', path: 'imported-group') }
+ let!(:parent_entity) do
+ create(
+ :bulk_import_entity,
+ destination_namespace: parent.full_path,
+ group: parent
+ )
+ end
+
+ let(:context) do
+ instance_double(
+ BulkImports::Pipeline::Context,
+ current_user: user,
+ entity: parent_entity
+ )
+ end
+
+ let(:subgroup_data) do
+ [
+ {
+ "name" => "subgroup",
+ "full_path" => "parent/subgroup"
+ }
+ ]
+ end
+
+ subject { described_class.new }
+
+ before do
+ allow_next_instance_of(BulkImports::Groups::Extractors::SubgroupsExtractor) do |extractor|
+ allow(extractor).to receive(:extract).and_return(subgroup_data)
+ end
+
+ parent.add_owner(user)
+ end
+
+ it 'creates entities for the subgroups' do
+ expect { subject.run(context) }.to change(BulkImports::Entity, :count).by(1)
+
+ subgroup_entity = BulkImports::Entity.last
+
+ expect(subgroup_entity.source_full_path).to eq 'parent/subgroup'
+ expect(subgroup_entity.destination_namespace).to eq 'imported-group'
+ expect(subgroup_entity.destination_name).to eq 'subgroup'
+ expect(subgroup_entity.parent_id).to eq parent_entity.id
+ end
+ end
+
+ describe 'pipeline parts' do
+ it { expect(described_class).to include_module(BulkImports::Pipeline) }
+ it { expect(described_class).to include_module(BulkImports::Pipeline::Attributes) }
+ it { expect(described_class).to include_module(BulkImports::Pipeline::Runner) }
+
+ it 'has extractors' do
+ expect(described_class.extractors).to contain_exactly(
+ klass: BulkImports::Groups::Extractors::SubgroupsExtractor,
+ options: nil
+ )
+ end
+
+ it 'has transformers' do
+ expect(described_class.transformers).to contain_exactly(
+ klass: BulkImports::Groups::Transformers::SubgroupToEntityTransformer,
+ options: nil
+ )
+ end
+
+ it 'has loaders' do
+ expect(described_class.loaders).to contain_exactly(
+ klass: BulkImports::Common::Loaders::EntityLoader,
+ options: nil
+ )
+ end
+ end
+end
diff --git a/spec/lib/bulk_imports/groups/transformers/group_attributes_transformer_spec.rb b/spec/lib/bulk_imports/groups/transformers/group_attributes_transformer_spec.rb
new file mode 100644
index 00000000000..28a7859915d
--- /dev/null
+++ b/spec/lib/bulk_imports/groups/transformers/group_attributes_transformer_spec.rb
@@ -0,0 +1,105 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe BulkImports::Groups::Transformers::GroupAttributesTransformer do
+ describe '#transform' do
+ let(:user) { create(:user) }
+ let(:parent) { create(:group) }
+ let(:group) { create(:group, name: 'My Source Group', parent: parent) }
+ let(:entity) do
+ instance_double(
+ BulkImports::Entity,
+ source_full_path: 'source/full/path',
+ destination_name: group.name,
+ destination_namespace: parent.full_path
+ )
+ end
+
+ let(:context) do
+ instance_double(
+ BulkImports::Pipeline::Context,
+ current_user: user,
+ entity: entity
+ )
+ end
+
+ let(:data) do
+ {
+ 'name' => 'source_name',
+ 'full_path' => 'source/full/path',
+ 'visibility' => 'private',
+ 'project_creation_level' => 'developer',
+ 'subgroup_creation_level' => 'maintainer'
+ }
+ end
+
+ subject { described_class.new }
+
+ it 'transforms name to destination name' do
+ transformed_data = subject.transform(context, data)
+
+ expect(transformed_data['name']).not_to eq('source_name')
+ expect(transformed_data['name']).to eq(group.name)
+ end
+
+ it 'removes full path' do
+ transformed_data = subject.transform(context, data)
+
+ expect(transformed_data).not_to have_key('full_path')
+ end
+
+ it 'transforms path to parameterized name' do
+ transformed_data = subject.transform(context, data)
+
+ expect(transformed_data['path']).to eq(group.name.parameterize)
+ end
+
+ it 'transforms visibility level' do
+ visibility = data['visibility']
+ transformed_data = subject.transform(context, data)
+
+ expect(transformed_data).not_to have_key('visibility')
+ expect(transformed_data['visibility_level']).to eq(Gitlab::VisibilityLevel.string_options[visibility])
+ end
+
+ it 'transforms project creation level' do
+ level = data['project_creation_level']
+ transformed_data = subject.transform(context, data)
+
+ expect(transformed_data['project_creation_level']).to eq(Gitlab::Access.project_creation_string_options[level])
+ end
+
+ it 'transforms subgroup creation level' do
+ level = data['subgroup_creation_level']
+ transformed_data = subject.transform(context, data)
+
+ expect(transformed_data['subgroup_creation_level']).to eq(Gitlab::Access.subgroup_creation_string_options[level])
+ end
+
+ describe 'parent group transformation' do
+ it 'sets parent id' do
+ transformed_data = subject.transform(context, data)
+
+ expect(transformed_data['parent_id']).to eq(parent.id)
+ end
+
+ context 'when destination namespace is user namespace' do
+ let(:entity) do
+ instance_double(
+ BulkImports::Entity,
+ source_full_path: 'source/full/path',
+ destination_name: group.name,
+ destination_namespace: user.namespace.full_path
+ )
+ end
+
+ it 'does not set parent id' do
+ transformed_data = subject.transform(context, data)
+
+ expect(transformed_data).not_to have_key('parent_id')
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/bulk_imports/groups/transformers/subgroup_to_entity_transformer_spec.rb b/spec/lib/bulk_imports/groups/transformers/subgroup_to_entity_transformer_spec.rb
new file mode 100644
index 00000000000..2f97a5721e7
--- /dev/null
+++ b/spec/lib/bulk_imports/groups/transformers/subgroup_to_entity_transformer_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe BulkImports::Groups::Transformers::SubgroupToEntityTransformer do
+ describe "#transform" do
+ it "transforms subgroups data in entity params" do
+ parent = create(:group)
+ parent_entity = instance_double(BulkImports::Entity, group: parent, id: 1)
+ context = instance_double(BulkImports::Pipeline::Context, entity: parent_entity)
+ subgroup_data = {
+ "name" => "subgroup",
+ "full_path" => "parent/subgroup"
+ }
+
+ expect(subject.transform(context, subgroup_data)).to eq(
+ source_type: :group_entity,
+ source_full_path: "parent/subgroup",
+ destination_name: "subgroup",
+ destination_namespace: parent.full_path,
+ parent_id: 1
+ )
+ end
+ end
+end
diff --git a/spec/lib/bulk_imports/importers/group_importer_spec.rb b/spec/lib/bulk_imports/importers/group_importer_spec.rb
new file mode 100644
index 00000000000..95ac5925c97
--- /dev/null
+++ b/spec/lib/bulk_imports/importers/group_importer_spec.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe BulkImports::Importers::GroupImporter do
+ let(:user) { create(:user) }
+ let(:bulk_import) { create(:bulk_import) }
+ let(:bulk_import_entity) { create(:bulk_import_entity, bulk_import: bulk_import) }
+ let(:bulk_import_configuration) { create(:bulk_import_configuration, bulk_import: bulk_import) }
+ let(:context) do
+ BulkImports::Pipeline::Context.new(
+ current_user: user,
+ entity: bulk_import_entity,
+ configuration: bulk_import_configuration
+ )
+ end
+
+ subject { described_class.new(bulk_import_entity) }
+
+ before do
+ allow(BulkImports::Pipeline::Context).to receive(:new).and_return(context)
+ stub_http_requests
+ end
+
+ describe '#execute' do
+ it "starts the entity and run its pipelines" do
+ expect(bulk_import_entity).to receive(:start).and_call_original
+ expect_to_run_pipeline BulkImports::Groups::Pipelines::GroupPipeline, context: context
+ expect_to_run_pipeline BulkImports::Groups::Pipelines::SubgroupEntitiesPipeline, context: context
+
+ subject.execute
+
+ expect(bulk_import_entity.reload).to be_finished
+ end
+ end
+
+ def expect_to_run_pipeline(klass, context:)
+ expect_next_instance_of(klass) do |pipeline|
+ expect(pipeline).to receive(:run).with(context)
+ end
+ end
+
+ def stub_http_requests
+ double_response = double(
+ code: 200,
+ success?: true,
+ parsed_response: {},
+ headers: {}
+ )
+
+ allow_next_instance_of(BulkImports::Clients::Http) do |client|
+ allow(client).to receive(:get).and_return(double_response)
+ allow(client).to receive(:post).and_return(double_response)
+ end
+ end
+end
diff --git a/spec/lib/bulk_imports/importers/groups_importer_spec.rb b/spec/lib/bulk_imports/importers/groups_importer_spec.rb
new file mode 100644
index 00000000000..4865034b0cd
--- /dev/null
+++ b/spec/lib/bulk_imports/importers/groups_importer_spec.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe BulkImports::Importers::GroupsImporter do
+ let_it_be(:bulk_import) { create(:bulk_import) }
+
+ subject { described_class.new(bulk_import.id) }
+
+ describe '#execute' do
+ context "when there is entities to be imported" do
+ let!(:bulk_import_entity) { create(:bulk_import_entity, bulk_import: bulk_import) }
+
+ it "starts the bulk_import and imports its entities" do
+ expect(BulkImports::Importers::GroupImporter).to receive(:new)
+ .with(bulk_import_entity).and_return(double(execute: true))
+ expect(BulkImportWorker).to receive(:perform_async).with(bulk_import.id)
+
+ subject.execute
+
+ expect(bulk_import.reload).to be_started
+ end
+ end
+
+ context "when there is no entities to be imported" do
+ it "starts the bulk_import and imports its entities" do
+ expect(BulkImports::Importers::GroupImporter).not_to receive(:new)
+ expect(BulkImportWorker).not_to receive(:perform_async)
+
+ subject.execute
+
+ expect(bulk_import.reload).to be_finished
+ end
+ end
+ end
+end
diff --git a/spec/lib/bulk_imports/pipeline/attributes_spec.rb b/spec/lib/bulk_imports/pipeline/attributes_spec.rb
new file mode 100644
index 00000000000..54c5dbd4cae
--- /dev/null
+++ b/spec/lib/bulk_imports/pipeline/attributes_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe BulkImports::Pipeline::Attributes do
+ describe 'pipeline attributes' do
+ before do
+ stub_const('BulkImports::Extractor', Class.new)
+ stub_const('BulkImports::Transformer', Class.new)
+ stub_const('BulkImports::Loader', Class.new)
+
+ klass = Class.new do
+ include BulkImports::Pipeline::Attributes
+
+ extractor BulkImports::Extractor, { foo: :bar }
+ transformer BulkImports::Transformer, { foo: :bar }
+ loader BulkImports::Loader, { foo: :bar }
+ end
+
+ stub_const('BulkImports::MyPipeline', klass)
+ end
+
+ describe 'getters' do
+ it 'retrieves class attributes' do
+ expect(BulkImports::MyPipeline.extractors).to contain_exactly({ klass: BulkImports::Extractor, options: { foo: :bar } })
+ expect(BulkImports::MyPipeline.transformers).to contain_exactly({ klass: BulkImports::Transformer, options: { foo: :bar } })
+ expect(BulkImports::MyPipeline.loaders).to contain_exactly({ klass: BulkImports::Loader, options: { foo: :bar } })
+ end
+ end
+
+ describe 'setters' do
+ it 'sets class attributes' do
+ klass = Class.new
+ options = { test: :test }
+
+ BulkImports::MyPipeline.extractor(klass, options)
+ BulkImports::MyPipeline.transformer(klass, options)
+ BulkImports::MyPipeline.loader(klass, options)
+
+ expect(BulkImports::MyPipeline.extractors)
+ .to contain_exactly(
+ { klass: BulkImports::Extractor, options: { foo: :bar } },
+ { klass: klass, options: options })
+
+ expect(BulkImports::MyPipeline.transformers)
+ .to contain_exactly(
+ { klass: BulkImports::Transformer, options: { foo: :bar } },
+ { klass: klass, options: options })
+
+ expect(BulkImports::MyPipeline.loaders)
+ .to contain_exactly(
+ { klass: BulkImports::Loader, options: { foo: :bar } },
+ { klass: klass, options: options })
+ end
+ end
+ end
+end
diff --git a/spec/lib/bulk_imports/pipeline/context_spec.rb b/spec/lib/bulk_imports/pipeline/context_spec.rb
new file mode 100644
index 00000000000..e9af6313ca4
--- /dev/null
+++ b/spec/lib/bulk_imports/pipeline/context_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe BulkImports::Pipeline::Context do
+ describe '#initialize' do
+ it 'initializes with permitted attributes' do
+ args = {
+ current_user: create(:user),
+ entity: create(:bulk_import_entity),
+ configuration: create(:bulk_import_configuration)
+ }
+
+ context = described_class.new(args)
+
+ args.each do |k, v|
+ expect(context.public_send(k)).to eq(v)
+ end
+ end
+
+ context 'when invalid argument is passed' do
+ it 'raises NoMethodError' do
+ expect { described_class.new(test: 'test').test }.to raise_exception(NoMethodError)
+ end
+ end
+ end
+end
diff --git a/spec/lib/bulk_imports/pipeline/runner_spec.rb b/spec/lib/bulk_imports/pipeline/runner_spec.rb
new file mode 100644
index 00000000000..8c882c799ec
--- /dev/null
+++ b/spec/lib/bulk_imports/pipeline/runner_spec.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe BulkImports::Pipeline::Runner do
+ describe 'pipeline runner' do
+ before do
+ extractor = Class.new do
+ def initialize(options = {}); end
+
+ def extract(context); end
+ end
+
+ transformer = Class.new do
+ def initialize(options = {}); end
+
+ def transform(context, entry); end
+ end
+
+ loader = Class.new do
+ def initialize(options = {}); end
+
+ def load(context, entry); end
+ end
+
+ stub_const('BulkImports::Extractor', extractor)
+ stub_const('BulkImports::Transformer', transformer)
+ stub_const('BulkImports::Loader', loader)
+
+ pipeline = Class.new do
+ include BulkImports::Pipeline
+
+ extractor BulkImports::Extractor
+ transformer BulkImports::Transformer
+ loader BulkImports::Loader
+ end
+
+ stub_const('BulkImports::MyPipeline', pipeline)
+ end
+
+ it 'runs pipeline extractor, transformer, loader' do
+ context = instance_double(
+ BulkImports::Pipeline::Context,
+ entity: instance_double(BulkImports::Entity, id: 1, source_type: 'group')
+ )
+ entries = [{ foo: :bar }]
+
+ expect_next_instance_of(BulkImports::Extractor) do |extractor|
+ expect(extractor).to receive(:extract).with(context).and_return(entries)
+ end
+
+ expect_next_instance_of(BulkImports::Transformer) do |transformer|
+ expect(transformer).to receive(:transform).with(context, entries.first).and_return(entries.first)
+ end
+
+ expect_next_instance_of(BulkImports::Loader) do |loader|
+ expect(loader).to receive(:load).with(context, entries.first)
+ end
+
+ expect_next_instance_of(Gitlab::Import::Logger) do |logger|
+ expect(logger).to receive(:info)
+ .with(message: "Pipeline started", pipeline: 'BulkImports::MyPipeline', entity: 1, entity_type: 'group')
+ expect(logger).to receive(:info)
+ .with(entity: 1, entity_type: 'group', extractor: 'BulkImports::Extractor')
+ expect(logger).to receive(:info)
+ .with(entity: 1, entity_type: 'group', transformer: 'BulkImports::Transformer')
+ expect(logger).to receive(:info)
+ .with(entity: 1, entity_type: 'group', loader: 'BulkImports::Loader')
+ end
+
+ BulkImports::MyPipeline.new.run(context)
+ end
+ end
+end