Commit 94c78fd6 authored by Douglas Barbosa Alexandre's avatar Douglas Barbosa Alexandre

Merge branch 'georgekoltsov/project-migration-members' into 'master'

Add BulkImports Project Members Migration & Refactor Group Members Migration

See merge request gitlab-org/gitlab!78455
parents ede5fb94 3b671ccd
...@@ -3,8 +3,12 @@ ...@@ -3,8 +3,12 @@
module BulkImports module BulkImports
module Groups module Groups
module Graphql module Graphql
module GetIterationsQuery class GetIterationsQuery
extend self attr_reader :context
def initialize(context:)
@context = context
end
def to_s def to_s
<<-'GRAPHQL' <<-'GRAPHQL'
...@@ -31,7 +35,7 @@ module BulkImports ...@@ -31,7 +35,7 @@ module BulkImports
GRAPHQL GRAPHQL
end end
def variables(context) def variables
{ {
full_path: context.entity.source_full_path, full_path: context.entity.source_full_path,
cursor: context.tracker.next_page, cursor: context.tracker.next_page,
......
...@@ -3,16 +3,18 @@ ...@@ -3,16 +3,18 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe BulkImports::Groups::Graphql::GetIterationsQuery do RSpec.describe BulkImports::Groups::Graphql::GetIterationsQuery do
it 'has a valid query' do let_it_be(:tracker) { create(:bulk_import_tracker) }
tracker = create(:bulk_import_tracker) let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) }
context = BulkImports::Pipeline::Context.new(tracker)
subject(:query) { described_class.new(context: context) }
query = GraphQL::Query.new( it 'has a valid query' do
parsed_query = GraphQL::Query.new(
GitlabSchema, GitlabSchema,
described_class.to_s, query.to_s,
variables: described_class.variables(context) variables: query.variables
) )
result = GitlabSchema.static_validator.validate(query) result = GitlabSchema.static_validator.validate(parsed_query)
expect(result[:errors]).to be_empty expect(result[:errors]).to be_empty
end end
...@@ -21,7 +23,7 @@ RSpec.describe BulkImports::Groups::Graphql::GetIterationsQuery do ...@@ -21,7 +23,7 @@ RSpec.describe BulkImports::Groups::Graphql::GetIterationsQuery do
it 'returns data path' do it 'returns data path' do
expected = %w[data group iterations nodes] expected = %w[data group iterations nodes]
expect(described_class.data_path).to eq(expected) expect(query.data_path).to eq(expected)
end end
end end
...@@ -29,7 +31,7 @@ RSpec.describe BulkImports::Groups::Graphql::GetIterationsQuery do ...@@ -29,7 +31,7 @@ RSpec.describe BulkImports::Groups::Graphql::GetIterationsQuery do
it 'returns pagination information path' do it 'returns pagination information path' do
expected = %w[data group iterations page_info] expected = %w[data group iterations page_info]
expect(described_class.page_info_path).to eq(expected) expect(query.page_info_path).to eq(expected)
end end
end end
end end
...@@ -7,7 +7,7 @@ RSpec.describe BulkImports::Groups::Stage do ...@@ -7,7 +7,7 @@ RSpec.describe BulkImports::Groups::Stage do
[ [
[0, BulkImports::Groups::Pipelines::GroupPipeline], [0, BulkImports::Groups::Pipelines::GroupPipeline],
[1, BulkImports::Groups::Pipelines::SubgroupEntitiesPipeline], [1, BulkImports::Groups::Pipelines::SubgroupEntitiesPipeline],
[1, BulkImports::Groups::Pipelines::MembersPipeline], [1, BulkImports::Common::Pipelines::MembersPipeline],
[1, BulkImports::Common::Pipelines::LabelsPipeline], [1, BulkImports::Common::Pipelines::LabelsPipeline],
[1, BulkImports::Common::Pipelines::MilestonesPipeline], [1, BulkImports::Common::Pipelines::MilestonesPipeline],
[1, BulkImports::Common::Pipelines::BadgesPipeline], [1, BulkImports::Common::Pipelines::BadgesPipeline],
......
...@@ -8,6 +8,7 @@ RSpec.describe BulkImports::Projects::Stage do ...@@ -8,6 +8,7 @@ RSpec.describe BulkImports::Projects::Stage do
[0, BulkImports::Projects::Pipelines::ProjectPipeline], [0, BulkImports::Projects::Pipelines::ProjectPipeline],
[1, BulkImports::Projects::Pipelines::RepositoryPipeline], [1, BulkImports::Projects::Pipelines::RepositoryPipeline],
[1, BulkImports::Projects::Pipelines::ProjectAttributesPipeline], [1, BulkImports::Projects::Pipelines::ProjectAttributesPipeline],
[1, BulkImports::Common::Pipelines::MembersPipeline],
[2, BulkImports::Common::Pipelines::LabelsPipeline], [2, BulkImports::Common::Pipelines::LabelsPipeline],
[2, BulkImports::Common::Pipelines::MilestonesPipeline], [2, BulkImports::Common::Pipelines::MilestonesPipeline],
[2, BulkImports::Common::Pipelines::BadgesPipeline], [2, BulkImports::Common::Pipelines::BadgesPipeline],
......
...@@ -5,15 +5,16 @@ module BulkImports ...@@ -5,15 +5,16 @@ module BulkImports
module Extractors module Extractors
class GraphqlExtractor class GraphqlExtractor
def initialize(options = {}) def initialize(options = {})
@query = options[:query] @query_klass = options[:query]
end end
def extract(context) def extract(context)
client = graphql_client(context) client = graphql_client(context)
query = query_klass.new(context: context)
response = client.execute( response = client.execute(
client.parse(query.to_s), client.parse(query.to_s),
query.variables(context) query.variables
).original_hash.deep_dup ).original_hash.deep_dup
BulkImports::Pipeline::ExtractedData.new( BulkImports::Pipeline::ExtractedData.new(
...@@ -24,7 +25,7 @@ module BulkImports ...@@ -24,7 +25,7 @@ module BulkImports
private private
attr_reader :query attr_reader :query_klass
def graphql_client(context) def graphql_client(context)
@graphql_client ||= BulkImports::Clients::Graphql.new( @graphql_client ||= BulkImports::Clients::Graphql.new(
......
# frozen_string_literal: true # frozen_string_literal: true
module BulkImports module BulkImports
module Groups module Common
module Graphql module Graphql
module GetMembersQuery class GetMembersQuery
extend self attr_reader :context
def initialize(context:)
@context = context
end
def to_s def to_s
<<-'GRAPHQL' <<-GRAPHQL
query($full_path: ID!, $cursor: String, $per_page: Int) { query($full_path: ID!, $cursor: String, $per_page: Int) {
group(fullPath: $full_path) { portable: #{context.entity.entity_type}(fullPath: $full_path) {
group_members: groupMembers(relations: DIRECT, first: $per_page, after: $cursor) { members: #{members_type}(relations: [DIRECT, INHERITED], first: $per_page, after: $cursor) {
page_info: pageInfo { page_info: pageInfo {
next_page: endCursor next_page: endCursor
has_next_page: hasNextPage has_next_page: hasNextPage
...@@ -32,7 +37,7 @@ module BulkImports ...@@ -32,7 +37,7 @@ module BulkImports
GRAPHQL GRAPHQL
end end
def variables(context) def variables
{ {
full_path: context.entity.source_full_path, full_path: context.entity.source_full_path,
cursor: context.tracker.next_page, cursor: context.tracker.next_page,
...@@ -40,10 +45,6 @@ module BulkImports ...@@ -40,10 +45,6 @@ module BulkImports
} }
end end
def base_path
%w[data group group_members]
end
def data_path def data_path
base_path << 'nodes' base_path << 'nodes'
end end
...@@ -51,6 +52,20 @@ module BulkImports ...@@ -51,6 +52,20 @@ module BulkImports
def page_info_path def page_info_path
base_path << 'page_info' base_path << 'page_info'
end end
private
def base_path
%w[data portable members]
end
def members_type
if context.entity.group?
'groupMembers'
else
'projectMembers'
end
end
end end
end end
end end
......
# frozen_string_literal: true
module BulkImports
module Common
module Pipelines
class MembersPipeline
include Pipeline
transformer Common::Transformers::ProhibitedAttributesTransformer
transformer BulkImports::Groups::Transformers::MemberAttributesTransformer
def extract(context)
graphql_extractor.extract(context)
end
def load(_context, data)
return unless data
user_id = data[:user_id]
# Current user is already a member
return if user_id == current_user.id
user_membership = existing_user_membership(user_id)
# User is already a member with higher existing (inherited) membership
return if user_membership && user_membership[:access_level] >= data[:access_level]
# Create new membership for any other access level
portable.members.create!(data)
end
private
def graphql_extractor
@graphql_extractor ||= BulkImports::Common::Extractors::GraphqlExtractor
.new(query: BulkImports::Common::Graphql::GetMembersQuery)
end
def existing_user_membership(user_id)
members_finder.execute.find_by_user_id(user_id)
end
def members_finder
@members_finder ||= if context.entity.group?
::GroupMembersFinder.new(portable, current_user)
else
::MembersFinder.new(portable, current_user)
end
end
end
end
end
end
...@@ -3,8 +3,12 @@ ...@@ -3,8 +3,12 @@
module BulkImports module BulkImports
module Groups module Groups
module Graphql module Graphql
module GetGroupQuery class GetGroupQuery
extend self attr_reader :context
def initialize(context:)
@context = context
end
def to_s def to_s
<<-'GRAPHQL' <<-'GRAPHQL'
...@@ -29,7 +33,7 @@ module BulkImports ...@@ -29,7 +33,7 @@ module BulkImports
GRAPHQL GRAPHQL
end end
def variables(context) def variables
{ full_path: context.entity.source_full_path } { full_path: context.entity.source_full_path }
end end
......
...@@ -3,8 +3,12 @@ ...@@ -3,8 +3,12 @@
module BulkImports module BulkImports
module Groups module Groups
module Graphql module Graphql
module GetProjectsQuery class GetProjectsQuery
extend self attr_reader :context
def initialize(context:)
@context = context
end
def to_s def to_s
<<-'GRAPHQL' <<-'GRAPHQL'
...@@ -25,7 +29,7 @@ module BulkImports ...@@ -25,7 +29,7 @@ module BulkImports
GRAPHQL GRAPHQL
end end
def variables(context) def variables
{ {
full_path: context.entity.source_full_path, full_path: context.entity.source_full_path,
cursor: context.tracker.next_page, cursor: context.tracker.next_page,
......
# frozen_string_literal: true
module BulkImports
module Groups
module Pipelines
class MembersPipeline
include Pipeline
extractor BulkImports::Common::Extractors::GraphqlExtractor,
query: BulkImports::Groups::Graphql::GetMembersQuery
transformer Common::Transformers::ProhibitedAttributesTransformer
transformer BulkImports::Groups::Transformers::MemberAttributesTransformer
def load(context, data)
return unless data
# Current user is already a member
return if data['user_id'].to_i == context.current_user.id
context.group.members.create!(data)
end
end
end
end
end
...@@ -16,7 +16,7 @@ module BulkImports ...@@ -16,7 +16,7 @@ module BulkImports
stage: 1 stage: 1
}, },
members: { members: {
pipeline: BulkImports::Groups::Pipelines::MembersPipeline, pipeline: BulkImports::Common::Pipelines::MembersPipeline,
stage: 1 stage: 1
}, },
labels: { labels: {
......
...@@ -5,50 +5,35 @@ module BulkImports ...@@ -5,50 +5,35 @@ module BulkImports
module Transformers module Transformers
class MemberAttributesTransformer class MemberAttributesTransformer
def transform(context, data) def transform(context, data)
data
.then { |data| add_user(data, context) }
.then { |data| add_access_level(data) }
.then { |data| add_author(data, context) }
end
private
def add_user(data, context)
user = find_user(data&.dig('user', 'public_email')) user = find_user(data&.dig('user', 'public_email'))
access_level = data&.dig('access_level', 'integer_value')
return unless data
return unless user return unless user
return unless valid_access_level?(access_level)
cache_source_user_id(data, user, context) cache_source_user_id(data, user, context)
data {
.except('user') user_id: user.id,
.merge('user_id' => user.id) access_level: access_level,
created_at: data['created_at'],
updated_at: data['updated_at'],
expires_at: data['expires_at'],
created_by_id: context.current_user.id
}
end end
private
def find_user(email) def find_user(email)
return unless email return unless email
User.find_by_any_email(email, confirmed: true) User.find_by_any_email(email, confirmed: true)
end end
def add_access_level(data)
access_level = data&.dig('access_level', 'integer_value')
return unless valid_access_level?(access_level)
data.merge('access_level' => access_level)
end
def valid_access_level?(access_level) def valid_access_level?(access_level)
Gitlab::Access Gitlab::Access.options_with_owner.value?(access_level)
.options_with_owner
.value?(access_level)
end
def add_author(data, context)
return unless data
data.merge('created_by_id' => context.current_user.id)
end end
def cache_source_user_id(data, user, context) def cache_source_user_id(data, user, context)
......
...@@ -3,9 +3,8 @@ ...@@ -3,9 +3,8 @@
module BulkImports module BulkImports
module Projects module Projects
module Graphql module Graphql
module GetProjectQuery class GetProjectQuery
extend Queryable include Queryable
extend self
def to_s def to_s
<<-'GRAPHQL' <<-'GRAPHQL'
......
...@@ -3,9 +3,8 @@ ...@@ -3,9 +3,8 @@
module BulkImports module BulkImports
module Projects module Projects
module Graphql module Graphql
module GetRepositoryQuery class GetRepositoryQuery
extend Queryable include Queryable
extend self
def to_s def to_s
<<-'GRAPHQL' <<-'GRAPHQL'
......
...@@ -3,9 +3,8 @@ ...@@ -3,9 +3,8 @@
module BulkImports module BulkImports
module Projects module Projects
module Graphql module Graphql
module GetSnippetRepositoryQuery class GetSnippetRepositoryQuery
extend Queryable include Queryable
extend self
def to_s def to_s
<<-'GRAPHQL' <<-'GRAPHQL'
...@@ -27,7 +26,7 @@ module BulkImports ...@@ -27,7 +26,7 @@ module BulkImports
GRAPHQL GRAPHQL
end end
def variables(context) def variables
{ {
full_path: context.entity.source_full_path, full_path: context.entity.source_full_path,
cursor: context.tracker.next_page, cursor: context.tracker.next_page,
......
...@@ -4,7 +4,13 @@ module BulkImports ...@@ -4,7 +4,13 @@ module BulkImports
module Projects module Projects
module Graphql module Graphql
module Queryable module Queryable
def variables(context) attr_reader :context
def initialize(context:)
@context = context
end
def variables
{ full_path: context.entity.source_full_path } { full_path: context.entity.source_full_path }
end end
......
...@@ -19,6 +19,10 @@ module BulkImports ...@@ -19,6 +19,10 @@ module BulkImports
pipeline: BulkImports::Projects::Pipelines::ProjectAttributesPipeline, pipeline: BulkImports::Projects::Pipelines::ProjectAttributesPipeline,
stage: 1 stage: 1
}, },
members: {
pipeline: BulkImports::Common::Pipelines::MembersPipeline,
stage: 1
},
labels: { labels: {
pipeline: BulkImports::Common::Pipelines::LabelsPipeline, pipeline: BulkImports::Common::Pipelines::LabelsPipeline,
stage: 2 stage: 2
......
...@@ -8,12 +8,15 @@ RSpec.describe BulkImports::Common::Extractors::GraphqlExtractor do ...@@ -8,12 +8,15 @@ RSpec.describe BulkImports::Common::Extractors::GraphqlExtractor do
let(:response) { double(original_hash: { 'data' => { 'foo' => 'bar' }, 'page_info' => {} }) } let(:response) { double(original_hash: { 'data' => { 'foo' => 'bar' }, 'page_info' => {} }) }
let(:options) do let(:options) do
{ {
query: double( query:
to_s: 'test', double(
variables: {}, new: double(
data_path: %w[data foo], to_s: 'test',
page_info_path: %w[data page_info] variables: {},
) data_path: %w[data foo],
page_info_path: %w[data page_info]
)
)
} }
end end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe BulkImports::Common::Graphql::GetMembersQuery do
let(:entity) { create(:bulk_import_entity, :group_entity) }
let(:tracker) { create(:bulk_import_tracker, entity: entity) }
let(:context) { BulkImports::Pipeline::Context.new(tracker) }
subject(:query) { described_class.new(context: context) }
it 'has a valid query' do
parsed_query = GraphQL::Query.new(
GitlabSchema,
query.to_s,
variables: query.variables
)
result = GitlabSchema.static_validator.validate(parsed_query)
expect(result[:errors]).to be_empty
end
describe '#data_path' do
it 'returns data path' do
expected = %w[data portable members nodes]
expect(query.data_path).to eq(expected)
end
end
describe '#page_info_path' do
it 'returns pagination information path' do
expected = %w[data portable members page_info]
expect(query.page_info_path).to eq(expected)
end
end
describe '#to_s' do
context 'when entity is group' do
it 'queries group & group members' do
expect(query.to_s).to include('group')
expect(query.to_s).to include('groupMembers')
end
end
context 'when entity is project' do
let(:entity) { create(:bulk_import_entity, :project_entity) }
it 'queries project & project members' do
expect(query.to_s).to include('project')
expect(query.to_s).to include('projectMembers')
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe BulkImports::Common::Pipelines::MembersPipeline do
let_it_be(:user) { create(:user) }
let_it_be(:bulk_import) { create(:bulk_import, user: user) }
let_it_be(:member_user1) { create(:user, email: 'email1@email.com') }
let_it_be(:member_user2) { create(:user, email: 'email2@email.com') }
let_it_be(:member_data) do
{
user_id: member_user1.id,
created_by_id: member_user2.id,
access_level: 30,
created_at: '2020-01-01T00:00:00Z',
updated_at: '2020-01-01T00:00:00Z',
expires_at: nil
}
end
let(:parent) { create(:group) }
let(:tracker) { create(:bulk_import_tracker, entity: entity) }
let(:context) { BulkImports::Pipeline::Context.new(tracker) }
let(:members) { portable.members.map { |m| m.slice(:user_id, :access_level) } }
subject(:pipeline) { described_class.new(context) }
def extracted_data(email:, has_next_page: false)
data = {
'created_at' => '2020-01-01T00:00:00Z',
'updated_at' => '2020-01-02T00:00:00Z',
'expires_at' => nil,
'access_level' => {
'integer_value' => 30
},
'user' => {
'public_email' => email
}
}
page_info = {
'has_next_page' => has_next_page,
'next_page' => has_next_page ? 'cursor' : nil
}
BulkImports::Pipeline::ExtractedData.new(data: data, page_info: page_info)
end
shared_examples 'members import' do
before do
portable.members.delete_all
end
describe '#run' do
it 'creates memberships for existing users' do
first_page = extracted_data(email: member_user1.email, has_next_page: true)
last_page = extracted_data(email: member_user2.email)
allow_next_instance_of(BulkImports::Common::Extractors::GraphqlExtractor) do |extractor|
allow(extractor).to receive(:extract).and_return(first_page, last_page)
end
expect { pipeline.run }.to change(portable.members, :count).by(2)
expect(members).to contain_exactly(
{ user_id: member_user1.id, access_level: 30 },
{ user_id: member_user2.id, access_level: 30 }
)
end
end
describe '#load' do
it 'creates new membership' do
expect { subject.load(context, member_data) }.to change(portable.members, :count).by(1)
member = portable.members.find_by_user_id(member_user1.id)
expect(member.user).to eq(member_user1)
expect(member.created_by).to eq(member_user2)
expect(member.access_level).to eq(30)
expect(member.created_at).to eq('2020-01-01T00:00:00Z')
expect(member.updated_at).to eq('2020-01-01T00:00:00Z')
expect(member.expires_at).to eq(nil)
end
context 'when user_id is current user id' do
it 'does not create new membership' do
data = { user_id: user.id }
expect { pipeline.load(context, data) }.not_to change(portable.members, :count)
end
end
context 'when data is nil' do
it 'does not create new membership' do
expect { pipeline.load(context, nil) }.not_to change(portable.members, :count)
end
end
context 'when user membership already exists with the same access level' do
it 'does not create new membership' do
portable.members.create!(member_data)
expect { pipeline.load(context, member_data) }.not_to change(portable.members, :count)
end
end
context 'when portable is in a parent group' do
let(:tracker) { create(:bulk_import_tracker, entity: entity_with_parent) }
before do
parent.members.create!(member_data)
end
context 'when the same membership exists in parent group' do
it 'does not create new membership' do
expect { pipeline.load(context, member_data) }.not_to change(portable_with_parent.members, :count)
end
end
context 'when membership with higher access level exists in parent group' do
it 'creates new direct membership' do
data = member_data.merge(access_level: Gitlab::Access::MAINTAINER)
expect { pipeline.load(context, data) }.to change(portable_with_parent.members, :count)
member = portable_with_parent.members.find_by_user_id(member_user1.id)
expect(member.access_level).to eq(Gitlab::Access::MAINTAINER)
end
end
context 'when membership with lower access level exists in parent group' do
it 'does not create new membership' do
data = member_data.merge(access_level: Gitlab::Access::GUEST)
expect { pipeline.load(context, data) }.not_to change(portable_with_parent.members, :count)
end
end
end
end
end
context 'when importing to group' do
let(:portable) { create(:group) }
let(:portable_with_parent) { create(:group, parent: parent) }
let(:entity) { create(:bulk_import_entity, :group_entity, group: portable, bulk_import: bulk_import) }
let(:entity_with_parent) { create(:bulk_import_entity, :group_entity, group: portable_with_parent, bulk_import: bulk_import) }
include_examples 'members import'
end
context 'when importing to project' do
let(:portable) { create(:project) }
let(:portable_with_parent) { create(:project, namespace: parent) }
let(:entity) { create(:bulk_import_entity, :project_entity, project: portable, bulk_import: bulk_import) }
let(:entity_with_parent) { create(:bulk_import_entity, :project_entity, project: portable_with_parent, bulk_import: bulk_import) }
include_examples 'members import'
end
end
...@@ -3,14 +3,27 @@ ...@@ -3,14 +3,27 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe BulkImports::Groups::Graphql::GetGroupQuery do RSpec.describe BulkImports::Groups::Graphql::GetGroupQuery do
let_it_be(:tracker) { create(:bulk_import_tracker) }
let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) }
subject(:query) { described_class.new(context: context) }
it 'has a valid query' do
parsed_query = GraphQL::Query.new(
GitlabSchema,
query.to_s,
variables: query.variables
)
result = GitlabSchema.static_validator.validate(parsed_query)
expect(result[:errors]).to be_empty
end
describe '#variables' do describe '#variables' do
it 'returns query variables based on entity information' do it 'returns query variables based on entity information' do
entity = double(source_full_path: 'test', bulk_import: nil) expected = { full_path: tracker.entity.source_full_path }
tracker = double(entity: entity)
context = BulkImports::Pipeline::Context.new(tracker)
expected = { full_path: entity.source_full_path }
expect(described_class.variables(context)).to eq(expected) expect(subject.variables).to eq(expected)
end end
end end
...@@ -18,7 +31,7 @@ RSpec.describe BulkImports::Groups::Graphql::GetGroupQuery do ...@@ -18,7 +31,7 @@ RSpec.describe BulkImports::Groups::Graphql::GetGroupQuery do
it 'returns data path' do it 'returns data path' do
expected = %w[data group] expected = %w[data group]
expect(described_class.data_path).to eq(expected) expect(subject.data_path).to eq(expected)
end end
end end
...@@ -26,7 +39,7 @@ RSpec.describe BulkImports::Groups::Graphql::GetGroupQuery do ...@@ -26,7 +39,7 @@ RSpec.describe BulkImports::Groups::Graphql::GetGroupQuery do
it 'returns pagination information path' do it 'returns pagination information path' do
expected = %w[data group page_info] expected = %w[data group page_info]
expect(described_class.page_info_path).to eq(expected) expect(subject.page_info_path).to eq(expected)
end end
end end
end end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe BulkImports::Groups::Graphql::GetMembersQuery do
it 'has a valid query' do
tracker = create(:bulk_import_tracker)
context = BulkImports::Pipeline::Context.new(tracker)
query = GraphQL::Query.new(
GitlabSchema,
described_class.to_s,
variables: described_class.variables(context)
)
result = GitlabSchema.static_validator.validate(query)
expect(result[:errors]).to be_empty
end
describe '#data_path' do
it 'returns data path' do
expected = %w[data group group_members nodes]
expect(described_class.data_path).to eq(expected)
end
end
describe '#page_info_path' do
it 'returns pagination information path' do
expected = %w[data group group_members page_info]
expect(described_class.page_info_path).to eq(expected)
end
end
end
...@@ -3,25 +3,25 @@ ...@@ -3,25 +3,25 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe BulkImports::Groups::Graphql::GetProjectsQuery do RSpec.describe BulkImports::Groups::Graphql::GetProjectsQuery do
describe '#variables' do let_it_be(:tracker) { create(:bulk_import_tracker) }
it 'returns valid variables based on entity information' do let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) }
tracker = create(:bulk_import_tracker)
context = BulkImports::Pipeline::Context.new(tracker) subject(:query) { described_class.new(context: context) }
query = GraphQL::Query.new( it 'has a valid query' do
GitlabSchema, parsed_query = GraphQL::Query.new(
described_class.to_s, GitlabSchema,
variables: described_class.variables(context) query.to_s,
) variables: query.variables
result = GitlabSchema.static_validator.validate(query) )
result = GitlabSchema.static_validator.validate(parsed_query)
expect(result[:errors]).to be_empty
end expect(result[:errors]).to be_empty
end
context 'with invalid variables' do context 'with invalid variables' do
it 'raises an error' do it 'raises an error' do
expect { GraphQL::Query.new(GitlabSchema, described_class.to_s, variables: 'invalid') }.to raise_error(ArgumentError) expect { GraphQL::Query.new(GitlabSchema, subject.to_s, variables: 'invalid') }.to raise_error(ArgumentError)
end
end end
end end
...@@ -29,7 +29,7 @@ RSpec.describe BulkImports::Groups::Graphql::GetProjectsQuery do ...@@ -29,7 +29,7 @@ RSpec.describe BulkImports::Groups::Graphql::GetProjectsQuery do
it 'returns data path' do it 'returns data path' do
expected = %w[data group projects nodes] expected = %w[data group projects nodes]
expect(described_class.data_path).to eq(expected) expect(subject.data_path).to eq(expected)
end end
end end
...@@ -37,7 +37,7 @@ RSpec.describe BulkImports::Groups::Graphql::GetProjectsQuery do ...@@ -37,7 +37,7 @@ RSpec.describe BulkImports::Groups::Graphql::GetProjectsQuery do
it 'returns pagination information path' do it 'returns pagination information path' do
expected = %w[data group projects page_info] expected = %w[data group projects page_info]
expect(described_class.page_info_path).to eq(expected) expect(subject.page_info_path).to eq(expected)
end end
end end
end end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe BulkImports::Groups::Pipelines::MembersPipeline do
let_it_be(:member_user1) { create(:user, email: 'email1@email.com') }
let_it_be(:member_user2) { create(:user, email: 'email2@email.com') }
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:bulk_import) { create(:bulk_import, user: user) }
let_it_be(:entity) { create(:bulk_import_entity, bulk_import: bulk_import, group: group) }
let_it_be(:tracker) { create(:bulk_import_tracker, entity: entity) }
let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) }
subject { described_class.new(context) }
describe '#run' do
it 'maps existing users to the imported group' do
first_page = extracted_data(email: member_user1.email, has_next_page: true)
last_page = extracted_data(email: member_user2.email)
allow_next_instance_of(BulkImports::Common::Extractors::GraphqlExtractor) do |extractor|
allow(extractor)
.to receive(:extract)
.and_return(first_page, last_page)
end
expect { subject.run }.to change(GroupMember, :count).by(2)
members = group.members.map { |m| m.slice(:user_id, :access_level) }
expect(members).to contain_exactly(
{ user_id: member_user1.id, access_level: 30 },
{ user_id: member_user2.id, access_level: 30 }
)
end
end
describe '#load' do
it 'does nothing when there is no data' do
expect { subject.load(context, nil) }.not_to change(GroupMember, :count)
end
it 'creates the member' do
data = {
'user_id' => member_user1.id,
'created_by_id' => member_user2.id,
'access_level' => 30,
'created_at' => '2020-01-01T00:00:00Z',
'updated_at' => '2020-01-01T00:00:00Z',
'expires_at' => nil
}
expect { subject.load(context, data) }.to change(GroupMember, :count).by(1)
member = group.members.last
expect(member.user).to eq(member_user1)
expect(member.created_by).to eq(member_user2)
expect(member.access_level).to eq(30)
expect(member.created_at).to eq('2020-01-01T00:00:00Z')
expect(member.updated_at).to eq('2020-01-01T00:00:00Z')
expect(member.expires_at).to eq(nil)
end
context 'when user_id is current user id' do
it 'does not create new member' do
data = { 'user_id' => user.id }
expect { subject.load(context, data) }.not_to change(GroupMember, :count)
end
end
end
describe 'pipeline parts' do
it { expect(described_class).to include_module(BulkImports::Pipeline) }
it { expect(described_class).to include_module(BulkImports::Pipeline::Runner) }
it 'has extractors' do
expect(described_class.get_extractor)
.to eq(
klass: BulkImports::Common::Extractors::GraphqlExtractor,
options: {
query: BulkImports::Groups::Graphql::GetMembersQuery
}
)
end
it 'has transformers' do
expect(described_class.transformers)
.to contain_exactly(
{ klass: BulkImports::Common::Transformers::ProhibitedAttributesTransformer, options: nil },
{ klass: BulkImports::Groups::Transformers::MemberAttributesTransformer, options: nil }
)
end
end
def extracted_data(email:, has_next_page: false)
data = {
'created_at' => '2020-01-01T00:00:00Z',
'updated_at' => '2020-01-01T00:00:00Z',
'expires_at' => nil,
'access_level' => {
'integer_value' => 30
},
'user' => {
'public_email' => email
}
}
page_info = {
'has_next_page' => has_next_page,
'next_page' => has_next_page ? 'cursor' : nil
}
BulkImports::Pipeline::ExtractedData.new(data: data, page_info: page_info)
end
end
...@@ -9,7 +9,7 @@ RSpec.describe BulkImports::Groups::Stage do ...@@ -9,7 +9,7 @@ RSpec.describe BulkImports::Groups::Stage do
[ [
[0, BulkImports::Groups::Pipelines::GroupPipeline], [0, BulkImports::Groups::Pipelines::GroupPipeline],
[1, BulkImports::Groups::Pipelines::SubgroupEntitiesPipeline], [1, BulkImports::Groups::Pipelines::SubgroupEntitiesPipeline],
[1, BulkImports::Groups::Pipelines::MembersPipeline], [1, BulkImports::Common::Pipelines::MembersPipeline],
[1, BulkImports::Common::Pipelines::LabelsPipeline], [1, BulkImports::Common::Pipelines::LabelsPipeline],
[1, BulkImports::Common::Pipelines::MilestonesPipeline], [1, BulkImports::Common::Pipelines::MilestonesPipeline],
[1, BulkImports::Common::Pipelines::BadgesPipeline], [1, BulkImports::Common::Pipelines::BadgesPipeline],
......
...@@ -48,12 +48,12 @@ RSpec.describe BulkImports::Groups::Transformers::MemberAttributesTransformer do ...@@ -48,12 +48,12 @@ RSpec.describe BulkImports::Groups::Transformers::MemberAttributesTransformer do
data = member_data(email: user.email) data = member_data(email: user.email)
expect(subject.transform(context, data)).to eq( expect(subject.transform(context, data)).to eq(
'access_level' => 30, access_level: 30,
'user_id' => user.id, user_id: user.id,
'created_by_id' => user.id, created_by_id: user.id,
'created_at' => '2020-01-01T00:00:00Z', created_at: '2020-01-01T00:00:00Z',
'updated_at' => '2020-01-01T00:00:00Z', updated_at: '2020-01-01T00:00:00Z',
'expires_at' => nil expires_at: nil
) )
end end
...@@ -62,12 +62,12 @@ RSpec.describe BulkImports::Groups::Transformers::MemberAttributesTransformer do ...@@ -62,12 +62,12 @@ RSpec.describe BulkImports::Groups::Transformers::MemberAttributesTransformer do
data = member_data(email: secondary_email) data = member_data(email: secondary_email)
expect(subject.transform(context, data)).to eq( expect(subject.transform(context, data)).to eq(
'access_level' => 30, access_level: 30,
'user_id' => user.id, user_id: user.id,
'created_by_id' => user.id, created_by_id: user.id,
'created_at' => '2020-01-01T00:00:00Z', created_at: '2020-01-01T00:00:00Z',
'updated_at' => '2020-01-01T00:00:00Z', updated_at: '2020-01-01T00:00:00Z',
'expires_at' => nil expires_at: nil
) )
end end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe BulkImports::Projects::Graphql::GetProjectQuery do
let_it_be(:tracker) { create(:bulk_import_tracker) }
let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) }
subject(:query) { described_class.new(context: context) }
it 'has a valid query' do
parsed_query = GraphQL::Query.new(
GitlabSchema,
query.to_s,
variables: query.variables
)
result = GitlabSchema.static_validator.validate(parsed_query)
expect(result[:errors]).to be_empty
end
it 'queries project based on source_full_path' do
expected = { full_path: tracker.entity.source_full_path }
expect(subject.variables).to eq(expected)
end
end
...@@ -3,19 +3,29 @@ ...@@ -3,19 +3,29 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe BulkImports::Projects::Graphql::GetRepositoryQuery do RSpec.describe BulkImports::Projects::Graphql::GetRepositoryQuery do
describe 'query repository based on full_path' do let_it_be(:tracker) { create(:bulk_import_tracker) }
let(:entity) { double(source_full_path: 'test', bulk_import: nil) } let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) }
let(:tracker) { double(entity: entity) }
let(:context) { BulkImports::Pipeline::Context.new(tracker) }
it 'returns project repository url' do subject(:query) { described_class.new(context: context) }
expect(described_class.to_s).to include('httpUrlToRepo')
end
it 'queries project based on source_full_path' do it 'has a valid query' do
expected = { full_path: entity.source_full_path } parsed_query = GraphQL::Query.new(
GitlabSchema,
query.to_s,
variables: query.variables
)
result = GitlabSchema.static_validator.validate(parsed_query)
expect(described_class.variables(context)).to eq(expected) expect(result[:errors]).to be_empty
end end
it 'returns project repository url' do
expect(subject.to_s).to include('httpUrlToRepo')
end
it 'queries project based on source_full_path' do
expected = { full_path: tracker.entity.source_full_path }
expect(subject.variables).to eq(expected)
end end
end end
...@@ -3,56 +3,56 @@ ...@@ -3,56 +3,56 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe BulkImports::Projects::Graphql::GetSnippetRepositoryQuery do RSpec.describe BulkImports::Projects::Graphql::GetSnippetRepositoryQuery do
describe 'query repository based on full_path' do let_it_be(:entity) { create(:bulk_import_entity) }
let_it_be(:entity) { create(:bulk_import_entity) } let_it_be(:tracker) { create(:bulk_import_tracker, entity: entity) }
let_it_be(:tracker) { create(:bulk_import_tracker, entity: entity) } let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) }
let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) }
it 'has a valid query' do
query = GraphQL::Query.new(
GitlabSchema,
described_class.to_s,
variables: described_class.variables(context)
)
result = GitlabSchema.static_validator.validate(query)
expect(result[:errors]).to be_empty
end
it 'returns snippet httpUrlToRepo' do subject(:query) { described_class.new(context: context) }
expect(described_class.to_s).to include('httpUrlToRepo')
end
it 'returns snippet createdAt' do it 'has a valid query' do
expect(described_class.to_s).to include('createdAt') parsed_query = GraphQL::Query.new(
end GitlabSchema,
query.to_s,
variables: query.variables
)
result = GitlabSchema.static_validator.validate(parsed_query)
it 'returns snippet title' do expect(result[:errors]).to be_empty
expect(described_class.to_s).to include('title') end
end
describe '.variables' do it 'returns snippet httpUrlToRepo' do
it 'queries project based on source_full_path and pagination' do expect(subject.to_s).to include('httpUrlToRepo')
expected = { full_path: entity.source_full_path, cursor: nil, per_page: 500 } end
expect(described_class.variables(context)).to eq(expected) it 'returns snippet createdAt' do
end expect(subject.to_s).to include('createdAt')
end
it 'returns snippet title' do
expect(subject.to_s).to include('title')
end
describe '.variables' do
it 'queries project based on source_full_path and pagination' do
expected = { full_path: entity.source_full_path, cursor: nil, per_page: 500 }
expect(subject.variables).to eq(expected)
end end
end
describe '.data_path' do describe '.data_path' do
it '.data_path returns data path' do it '.data_path returns data path' do
expected = %w[data project snippets nodes] expected = %w[data project snippets nodes]
expect(described_class.data_path).to eq(expected) expect(subject.data_path).to eq(expected)
end
end end
end
describe '.page_info_path' do describe '.page_info_path' do
it '.page_info_path returns pagination information path' do it '.page_info_path returns pagination information path' do
expected = %w[data project snippets page_info] expected = %w[data project snippets page_info]
expect(described_class.page_info_path).to eq(expected) expect(subject.page_info_path).to eq(expected)
end
end end
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment