diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-02-26 21:09:24 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-02-26 21:09:24 +0300 |
commit | 619d0b6922a6cf95d291fbbf5fa3d09e772a1ea8 (patch) | |
tree | fb8f8e036cec1b32166206bb5102af6c5dca8cfe /spec/lib/gitlab/import_export/group | |
parent | 17ab40ca089e1aef61a83f77ab6df62a72f6ce06 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/lib/gitlab/import_export/group')
4 files changed, 541 insertions, 0 deletions
diff --git a/spec/lib/gitlab/import_export/group/object_builder_spec.rb b/spec/lib/gitlab/import_export/group/object_builder_spec.rb new file mode 100644 index 00000000000..781670b0aa5 --- /dev/null +++ b/spec/lib/gitlab/import_export/group/object_builder_spec.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::ImportExport::Group::ObjectBuilder do + let(:group) { create(:group) } + let(:base_attributes) do + { + 'title' => 'title', + 'description' => 'description', + 'group' => group + } + end + + context 'labels' do + let(:label_attributes) { base_attributes.merge('type' => 'GroupLabel') } + + it 'finds the existing group label' do + group_label = create(:group_label, base_attributes) + + expect(described_class.build(Label, label_attributes)).to eq(group_label) + end + + it 'creates a new label' do + label = described_class.build(Label, label_attributes) + + expect(label.persisted?).to be true + end + + context 'when description is an empty string' do + let(:label_attributes) { base_attributes.merge('type' => 'GroupLabel', 'description' => '') } + + it 'finds the existing group label' do + group_label = create(:group_label, label_attributes) + + expect(described_class.build(Label, label_attributes)).to eq(group_label) + end + end + end + + context 'milestones' do + it 'finds the existing group milestone' do + milestone = create(:milestone, base_attributes) + + expect(described_class.build(Milestone, base_attributes)).to eq(milestone) + end + + it 'creates a new milestone' do + milestone = described_class.build(Milestone, base_attributes) + + expect(milestone.persisted?).to be true + end + end + + describe '#initialize' do + context 'when attributes contain description as empty string' do + let(:attributes) { base_attributes.merge('description' => '') } + + it 'converts empty string to nil' do + builder = described_class.new(Label, attributes) + + expect(builder.send(:attributes)).to include({ 'description' => nil }) + end + end + end +end diff --git a/spec/lib/gitlab/import_export/group/relation_factory_spec.rb b/spec/lib/gitlab/import_export/group/relation_factory_spec.rb new file mode 100644 index 00000000000..332648d5c89 --- /dev/null +++ b/spec/lib/gitlab/import_export/group/relation_factory_spec.rb @@ -0,0 +1,120 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::ImportExport::Group::RelationFactory do + let(:group) { create(:group) } + let(:members_mapper) { double('members_mapper').as_null_object } + let(:user) { create(:admin) } + let(:excluded_keys) { [] } + let(:created_object) do + described_class.create(relation_sym: relation_sym, + relation_hash: relation_hash, + members_mapper: members_mapper, + object_builder: Gitlab::ImportExport::Group::ObjectBuilder, + user: user, + importable: group, + excluded_keys: excluded_keys) + end + + context 'label object' do + let(:relation_sym) { :group_label } + let(:id) { random_id } + let(:original_group_id) { random_id } + + let(:relation_hash) do + { + 'id' => 123456, + 'title' => 'Bruffefunc', + 'color' => '#1d2da4', + 'project_id' => nil, + 'created_at' => '2019-11-20T17:02:20.546Z', + 'updated_at' => '2019-11-20T17:02:20.546Z', + 'template' => false, + 'description' => 'Description', + 'group_id' => original_group_id, + 'type' => 'GroupLabel', + 'priorities' => [], + 'textColor' => '#FFFFFF' + } + end + + it 'does not have the original ID' do + expect(created_object.id).not_to eq(id) + end + + it 'does not have the original group_id' do + expect(created_object.group_id).not_to eq(original_group_id) + end + + it 'has the new group_id' do + expect(created_object.group_id).to eq(group.id) + end + + context 'excluded attributes' do + let(:excluded_keys) { %w[description] } + + it 'are removed from the imported object' do + expect(created_object.description).to be_nil + end + end + end + + context 'Notes user references' do + let(:relation_sym) { :notes } + let(:new_user) { create(:user) } + let(:exported_member) do + { + 'id' => 111, + 'access_level' => 30, + 'source_id' => 1, + 'source_type' => 'Namespace', + 'user_id' => 3, + 'notification_level' => 3, + 'created_at' => '2016-11-18T09:29:42.634Z', + 'updated_at' => '2016-11-18T09:29:42.634Z', + 'user' => { + 'id' => 999, + 'email' => new_user.email, + 'username' => new_user.username + } + } + end + + let(:relation_hash) do + { + 'id' => 4947, + 'note' => 'note', + 'noteable_type' => 'Epic', + 'author_id' => 999, + 'created_at' => '2016-11-18T09:29:42.634Z', + 'updated_at' => '2016-11-18T09:29:42.634Z', + 'project_id' => 1, + 'attachment' => { + 'url' => nil + }, + 'noteable_id' => 377, + 'system' => true, + 'author' => { + 'name' => 'Administrator' + }, + 'events' => [] + } + end + + let(:members_mapper) do + Gitlab::ImportExport::MembersMapper.new( + exported_members: [exported_member], + user: user, + importable: group) + end + + it 'maps the right author to the imported note' do + expect(created_object.author).to eq(new_user) + end + end + + def random_id + rand(1000..10000) + end +end diff --git a/spec/lib/gitlab/import_export/group/tree_restorer_spec.rb b/spec/lib/gitlab/import_export/group/tree_restorer_spec.rb new file mode 100644 index 00000000000..5584f1503f7 --- /dev/null +++ b/spec/lib/gitlab/import_export/group/tree_restorer_spec.rb @@ -0,0 +1,153 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::ImportExport::Group::TreeRestorer do + include ImportExport::CommonUtil + + let(:shared) { Gitlab::ImportExport::Shared.new(group) } + + describe 'restore group tree' do + before(:context) do + # Using an admin for import, so we can check assignment of existing members + user = create(:admin, email: 'root@gitlabexample.com') + create(:user, email: 'adriene.mcclure@gitlabexample.com') + create(:user, email: 'gwendolyn_robel@gitlabexample.com') + + RSpec::Mocks.with_temporary_scope do + @group = create(:group, name: 'group', path: 'group') + @shared = Gitlab::ImportExport::Shared.new(@group) + + setup_import_export_config('group_exports/complex') + + group_tree_restorer = described_class.new(user: user, shared: @shared, group: @group, group_hash: nil) + + @restored_group_json = group_tree_restorer.restore + end + end + + context 'JSON' do + it 'restores models based on JSON' do + expect(@restored_group_json).to be_truthy + end + + it 'has the group description' do + expect(Group.find_by_path('group').description).to eq('Group Description') + end + + it 'has group labels' do + expect(@group.labels.count).to eq(10) + end + + context 'issue boards' do + it 'has issue boards' do + expect(@group.boards.count).to eq(1) + end + + it 'has board label lists' do + lists = @group.boards.find_by(name: 'first board').lists + + expect(lists.count).to eq(3) + expect(lists.first.label.title).to eq('TSL') + expect(lists.second.label.title).to eq('Sosync') + end + end + + it 'has badges' do + expect(@group.badges.count).to eq(1) + end + + it 'has milestones' do + expect(@group.milestones.count).to eq(5) + end + + it 'has group children' do + expect(@group.children.count).to eq(2) + end + + it 'has group members' do + expect(@group.members.map(&:user).map(&:email)).to contain_exactly('root@gitlabexample.com', 'adriene.mcclure@gitlabexample.com', 'gwendolyn_robel@gitlabexample.com') + end + end + end + + context 'excluded attributes' do + let!(:source_user) { create(:user, id: 123) } + let!(:importer_user) { create(:user) } + let(:group) { create(:group) } + let(:shared) { Gitlab::ImportExport::Shared.new(group) } + let(:group_tree_restorer) { described_class.new(user: importer_user, shared: shared, group: group, group_hash: nil) } + let(:group_json) { ActiveSupport::JSON.decode(IO.read(File.join(shared.export_path, 'group.json'))) } + + shared_examples 'excluded attributes' do + excluded_attributes = %w[ + id + owner_id + parent_id + created_at + updated_at + runners_token + runners_token_encrypted + saml_discovery_token + ] + + before do + group.add_owner(importer_user) + + setup_import_export_config('group_exports/complex') + end + + excluded_attributes.each do |excluded_attribute| + it 'does not allow override of excluded attributes' do + expect(group_json[excluded_attribute]).not_to eq(group.public_send(excluded_attribute)) + end + end + end + + include_examples 'excluded attributes' + end + + context 'group.json file access check' do + let(:user) { create(:user) } + let!(:group) { create(:group, name: 'group2', path: 'group2') } + let(:group_tree_restorer) { described_class.new(user: user, shared: shared, group: group, group_hash: nil) } + let(:restored_group_json) { group_tree_restorer.restore } + + it 'does not read a symlink' do + Dir.mktmpdir do |tmpdir| + setup_symlink(tmpdir, 'group.json') + allow(shared).to receive(:export_path).and_call_original + + expect(group_tree_restorer.restore).to eq(false) + expect(shared.errors).to include('Incorrect JSON format') + end + end + end + + context 'group visibility levels' do + let(:user) { create(:user) } + let(:shared) { Gitlab::ImportExport::Shared.new(group) } + let(:group_tree_restorer) { described_class.new(user: user, shared: shared, group: group, group_hash: nil) } + + before do + setup_import_export_config(filepath) + + group_tree_restorer.restore + end + + shared_examples 'with visibility level' do |visibility_level, expected_visibilities| + context "when visibility level is #{visibility_level}" do + let(:group) { create(:group, visibility_level) } + let(:filepath) { "group_exports/visibility_levels/#{visibility_level}" } + + it "imports all subgroups as #{visibility_level}" do + expect(group.children.map(&:visibility_level)).to eq(expected_visibilities) + end + end + end + + include_examples 'with visibility level', :public, [20, 10, 0] + include_examples 'with visibility level', :private, [0, 0, 0] + include_examples 'with visibility level', :internal, [10, 10, 0] + end +end diff --git a/spec/lib/gitlab/import_export/group/tree_saver_spec.rb b/spec/lib/gitlab/import_export/group/tree_saver_spec.rb new file mode 100644 index 00000000000..845eb8e308b --- /dev/null +++ b/spec/lib/gitlab/import_export/group/tree_saver_spec.rb @@ -0,0 +1,202 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::ImportExport::Group::TreeSaver do + describe 'saves the group tree into a json object' do + let(:shared) { Gitlab::ImportExport::Shared.new(group) } + let(:group_tree_saver) { described_class.new(group: group, current_user: user, shared: shared) } + let(:export_path) { "#{Dir.tmpdir}/group_tree_saver_spec" } + let(:user) { create(:user) } + let!(:group) { setup_group } + + before do + group.add_maintainer(user) + allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) + end + + after do + FileUtils.rm_rf(export_path) + end + + it 'saves group successfully' do + expect(group_tree_saver.save).to be true + end + + context ':export_fast_serialize feature flag checks' do + before do + expect(Gitlab::ImportExport::Reader).to receive(:new).with(shared: shared, config: group_config).and_return(reader) + expect(reader).to receive(:group_tree).and_return(group_tree) + end + + let(:reader) { instance_double('Gitlab::ImportExport::Reader') } + let(:group_config) { Gitlab::ImportExport::Config.new(config: Gitlab::ImportExport.group_config_file).to_h } + let(:group_tree) do + { + include: [{ milestones: { include: [] } }], + preload: { milestones: nil } + } + end + + context 'when :export_fast_serialize feature is enabled' do + let(:serializer) { instance_double(Gitlab::ImportExport::FastHashSerializer) } + + before do + stub_feature_flags(export_fast_serialize: true) + + expect(Gitlab::ImportExport::FastHashSerializer).to receive(:new).with(group, group_tree).and_return(serializer) + end + + it 'uses FastHashSerializer' do + expect(serializer).to receive(:execute) + + group_tree_saver.save + end + end + + context 'when :export_fast_serialize feature is disabled' do + before do + stub_feature_flags(export_fast_serialize: false) + end + + it 'is serialized via built-in `as_json`' do + expect(group).to receive(:as_json).with(group_tree).and_call_original + + group_tree_saver.save + end + end + end + + # It is mostly duplicated in + # `spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb` + # except: + # context 'with description override' do + # context 'group members' do + # ^ These are specific for the Group::TreeSaver + context 'JSON' do + let(:saved_group_json) do + group_tree_saver.save + group_json(group_tree_saver.full_path) + end + + it 'saves the correct json' do + expect(saved_group_json).to include({ 'description' => 'description' }) + end + + it 'has milestones' do + expect(saved_group_json['milestones']).not_to be_empty + end + + it 'has labels' do + expect(saved_group_json['labels']).not_to be_empty + end + + it 'has boards' do + expect(saved_group_json['boards']).not_to be_empty + end + + it 'has board label list' do + expect(saved_group_json['boards'].first['lists']).not_to be_empty + end + + it 'has group members' do + expect(saved_group_json['members']).not_to be_empty + end + + it 'has priorities associated to labels' do + expect(saved_group_json['labels'].first['priorities']).not_to be_empty + end + + it 'has badges' do + expect(saved_group_json['badges']).not_to be_empty + end + + context 'group children' do + let(:children) { group.children } + + it 'exports group children' do + expect(saved_group_json['children'].length).to eq(children.count) + end + + it 'exports group children of children' do + expect(saved_group_json['children'].first['children'].length).to eq(children.first.children.count) + end + end + + context 'group members' do + let(:user2) { create(:user, email: 'group@member.com') } + let(:member_emails) do + saved_group_json['members'].map do |pm| + pm['user']['email'] + end + end + + before do + group.add_developer(user2) + end + + it 'exports group members as group owner' do + group.add_owner(user) + + expect(member_emails).to include('group@member.com') + end + + context 'as admin' do + let(:user) { create(:admin) } + + it 'exports group members as admin' do + expect(member_emails).to include('group@member.com') + end + + it 'exports group members' do + member_types = saved_group_json['members'].map { |pm| pm['source_type'] } + + expect(member_types).to all(eq('Namespace')) + end + end + end + + context 'group attributes' do + shared_examples 'excluded attributes' do + excluded_attributes = %w[ + id + owner_id + parent_id + created_at + updated_at + runners_token + runners_token_encrypted + saml_discovery_token + ] + + excluded_attributes.each do |excluded_attribute| + it 'does not contain excluded attribute' do + expect(saved_group_json).not_to include(excluded_attribute => group.public_send(excluded_attribute)) + end + end + end + + include_examples 'excluded attributes' + end + end + end + + def setup_group + group = create(:group, description: 'description') + sub_group = create(:group, description: 'description', parent: group) + create(:group, description: 'description', parent: sub_group) + create(:milestone, group: group) + create(:group_badge, group: group) + group_label = create(:group_label, group: group) + create(:label_priority, label: group_label, priority: 1) + board = create(:board, group: group) + create(:list, board: board, label: group_label) + create(:group_badge, group: group) + + group + end + + def group_json(filename) + JSON.parse(IO.read(filename)) + end +end |