debian-mirror-gitlab/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb
2021-02-22 17:27:13 +05:30

1045 lines
38 KiB
Ruby

# frozen_string_literal: true
require 'spec_helper'
def match_mr1_note(content_regex)
MergeRequest.find_by(title: 'MR1').notes.select { |n| n.note.match(/#{content_regex}/)}.first
end
RSpec.describe Gitlab::ImportExport::Project::TreeRestorer do
include ImportExport::CommonUtil
using RSpec::Parameterized::TableSyntax
let(:shared) { project.import_export_shared }
RSpec.shared_examples 'project tree restorer work properly' do |reader, ndjson_enabled|
describe 'restore project tree' do
before_all do
# Using an admin for import, so we can check assignment of existing members
@user = create(:admin)
@existing_members = [
create(:user, email: 'bernard_willms@gitlabexample.com'),
create(:user, email: 'saul_will@gitlabexample.com')
]
RSpec::Mocks.with_temporary_scope do
@project = create(:project, :builds_enabled, :issues_disabled, name: 'project', path: 'project')
@shared = @project.import_export_shared
stub_all_feature_flags
stub_feature_flags(project_import_ndjson: ndjson_enabled)
setup_import_export_config('complex')
setup_reader(reader)
allow_any_instance_of(Repository).to receive(:fetch_source_branch!).and_return(true)
allow_any_instance_of(Gitlab::Git::Repository).to receive(:branch_exists?).and_return(false)
expect(@shared).not_to receive(:error)
expect_any_instance_of(Gitlab::Git::Repository).to receive(:create_branch).with('feature', 'DCBA')
allow_any_instance_of(Gitlab::Git::Repository).to receive(:create_branch)
project_tree_restorer = described_class.new(user: @user, shared: @shared, project: @project)
@restored_project_json = project_tree_restorer.restore
end
end
context 'JSON' do
it 'restores models based on JSON' do
expect(@restored_project_json).to be_truthy
end
it 'restore correct project features' do
project = Project.find_by_path('project')
expect(project.project_feature.issues_access_level).to eq(ProjectFeature::PRIVATE)
expect(project.project_feature.builds_access_level).to eq(ProjectFeature::PRIVATE)
expect(project.project_feature.snippets_access_level).to eq(ProjectFeature::PRIVATE)
expect(project.project_feature.wiki_access_level).to eq(ProjectFeature::PRIVATE)
expect(project.project_feature.merge_requests_access_level).to eq(ProjectFeature::PRIVATE)
end
it 'has the project description' do
expect(Project.find_by_path('project').description).to eq('Nisi et repellendus ut enim quo accusamus vel magnam.')
end
it 'has the same label associated to two issues' do
expect(ProjectLabel.find_by_title('test2').issues.count).to eq(2)
end
it 'has milestones associated to two separate issues' do
expect(Milestone.find_by_description('test milestone').issues.count).to eq(2)
end
context 'when importing a project with cached_markdown_version and note_html' do
context 'for an Issue' do
it 'does not import note_html' do
note_content = 'Quo reprehenderit aliquam qui dicta impedit cupiditate eligendi'
issue_note = Issue.find_by(description: 'Aliquam enim illo et possimus.').notes.select { |n| n.note.match(/#{note_content}/)}.first
expect(issue_note.note_html).to match(/#{note_content}/)
end
end
context 'for a Merge Request' do
it 'does not import note_html' do
note_content = 'Sit voluptatibus eveniet architecto quidem'
merge_request_note = match_mr1_note(note_content)
expect(merge_request_note.note_html).to match(/#{note_content}/)
end
end
context 'merge request system note metadata' do
it 'restores title action for unmark wip' do
merge_request_note = match_mr1_note('unmarked as a \\*\\*Work In Progress\\*\\*')
expect(merge_request_note.noteable_type).to eq('MergeRequest')
expect(merge_request_note.system).to eq(true)
expect(merge_request_note.system_note_metadata.action).to eq('title')
expect(merge_request_note.system_note_metadata.commit_count).to be_nil
end
it 'restores commit action and commit count for pushing 3 commits' do
merge_request_note = match_mr1_note('added 3 commits')
expect(merge_request_note.noteable_type).to eq('MergeRequest')
expect(merge_request_note.system).to eq(true)
expect(merge_request_note.system_note_metadata.action).to eq('commit')
expect(merge_request_note.system_note_metadata.commit_count).to eq(3)
end
end
end
it 'creates a valid pipeline note' do
expect(Ci::Pipeline.find_by_sha('sha-notes').notes).not_to be_empty
end
it 'pipeline has the correct user ID' do
expect(Ci::Pipeline.find_by_sha('sha-notes').user_id).to eq(@user.id)
end
it 'restores pipelines with missing ref' do
expect(Ci::Pipeline.where(ref: nil)).not_to be_empty
end
it 'restores pipeline for merge request' do
pipeline = Ci::Pipeline.find_by_sha('048721d90c449b244b7b4c53a9186b04330174ec')
expect(pipeline).to be_valid
expect(pipeline.tag).to be_falsey
expect(pipeline.source).to eq('merge_request_event')
expect(pipeline.merge_request.id).to be > 0
expect(pipeline.merge_request.target_branch).to eq('feature')
expect(pipeline.merge_request.source_branch).to eq('feature_conflict')
end
it 'restores pipelines based on ascending id order' do
expected_ordered_shas = %w[
2ea1f3dec713d940208fb5ce4a38765ecb5d3f73
ce84140e8b878ce6e7c4d298c7202ff38170e3ac
048721d90c449b244b7b4c53a9186b04330174ec
sha-notes
5f923865dde3436854e9ceb9cdb7815618d4e849
d2d430676773caa88cdaf7c55944073b2fd5561a
2ea1f3dec713d940208fb5ce4a38765ecb5d3f73
]
project = Project.find_by_path('project')
project.ci_pipelines.order(:id).each_with_index do |pipeline, i|
expect(pipeline['sha']).to eq expected_ordered_shas[i]
end
end
it 'preserves updated_at on issues' do
issue = Issue.find_by(description: 'Aliquam enim illo et possimus.')
expect(issue.reload.updated_at.to_s).to eq('2016-06-14 15:02:47 UTC')
end
it 'has multiple issue assignees' do
expect(Issue.find_by(title: 'Voluptatem').assignees).to contain_exactly(@user, *@existing_members)
expect(Issue.find_by(title: 'Issue without assignees').assignees).to be_empty
end
it 'restores timelogs for issues' do
timelog = Issue.find_by(title: 'issue_with_timelogs').timelogs.last
aggregate_failures do
expect(timelog.time_spent).to eq(72000)
expect(timelog.spent_at).to eq("2019-12-27T00:00:00.000Z")
end
end
it 'contains the merge access levels on a protected branch' do
expect(ProtectedBranch.first.merge_access_levels).not_to be_empty
end
it 'contains the push access levels on a protected branch' do
expect(ProtectedBranch.first.push_access_levels).not_to be_empty
end
it 'contains the create access levels on a protected tag' do
expect(ProtectedTag.first.create_access_levels).not_to be_empty
end
it 'restores issue resource label events' do
expect(Issue.find_by(title: 'Voluptatem').resource_label_events).not_to be_empty
end
it 'restores merge requests resource label events' do
expect(MergeRequest.find_by(title: 'MR1').resource_label_events).not_to be_empty
end
it 'restores suggestion' do
note = Note.find_by("note LIKE 'Saepe asperiores exercitationem non dignissimos laborum reiciendis et ipsum%'")
expect(note.suggestions.count).to eq(1)
expect(note.suggestions.first.from_content).to eq("Original line\n")
end
context 'event at forth level of the tree' do
let(:event) { Event.find_by(action: 6) }
it 'restores the event' do
expect(event).not_to be_nil
end
it 'has the action' do
expect(event.action).not_to be_nil
end
it 'event belongs to note, belongs to merge request, belongs to a project' do
expect(event.note.noteable.project).not_to be_nil
end
end
it 'has the correct data for merge request diff files' do
expect(MergeRequestDiffFile.where.not(diff: nil).count).to eq(55)
end
it 'has the correct data for merge request diff commits' do
expect(MergeRequestDiffCommit.count).to eq(77)
end
it 'has the correct data for merge request latest_merge_request_diff' do
MergeRequest.find_each do |merge_request|
expect(merge_request.latest_merge_request_diff_id).to eq(merge_request.merge_request_diffs.maximum(:id))
end
end
it 'has labels associated to label links, associated to issues' do
expect(Label.first.label_links.first.target).not_to be_nil
end
it 'has project labels' do
expect(ProjectLabel.count).to eq(3)
end
it 'has no group labels' do
expect(GroupLabel.count).to eq(0)
end
it 'has issue boards' do
expect(Project.find_by_path('project').boards.count).to eq(1)
end
it 'has lists associated with the issue board' do
expect(Project.find_by_path('project').boards.find_by_name('TestBoardABC').lists.count).to eq(3)
end
it 'has a project feature' do
expect(@project.project_feature).not_to be_nil
end
it 'has custom attributes' do
expect(@project.custom_attributes.count).to eq(2)
end
it 'has badges' do
expect(@project.project_badges.count).to eq(2)
end
it 'has snippets' do
expect(@project.snippets.count).to eq(1)
end
it 'has award emoji for a snippet' do
award_emoji = @project.snippets.first.award_emoji
expect(award_emoji.map(&:name)).to contain_exactly('thumbsup', 'coffee')
end
it 'snippet has notes' do
expect(@project.snippets.first.notes.count).to eq(1)
end
it 'snippet has award emojis on notes' do
award_emoji = @project.snippets.first.notes.first.award_emoji.first
expect(award_emoji.name).to eq('thumbsup')
end
it 'restores `ci_cd_settings` : `group_runners_enabled` setting' do
expect(@project.ci_cd_settings.group_runners_enabled?).to eq(false)
end
it 'restores `auto_devops`' do
expect(@project.auto_devops_enabled?).to eq(true)
expect(@project.auto_devops.deploy_strategy).to eq('continuous')
end
it 'restores zoom meetings' do
meetings = @project.issues.first.zoom_meetings
expect(meetings.count).to eq(1)
expect(meetings.first.url).to eq('https://zoom.us/j/123456789')
end
it 'restores sentry issues' do
sentry_issue = @project.issues.first.sentry_issue
expect(sentry_issue.sentry_issue_identifier).to eq(1234567891)
end
it 'has award emoji for an issue' do
award_emoji = @project.issues.first.award_emoji.first
expect(award_emoji.name).to eq('musical_keyboard')
end
it 'has award emoji for a note in an issue' do
award_emoji = @project.issues.first.notes.first.award_emoji.first
expect(award_emoji.name).to eq('clapper')
end
it 'restores container_expiration_policy' do
policy = Project.find_by_path('project').container_expiration_policy
aggregate_failures do
expect(policy).to be_an_instance_of(ContainerExpirationPolicy)
expect(policy).to be_persisted
expect(policy.cadence).to eq('3month')
end
end
it 'restores error_tracking_setting' do
setting = @project.error_tracking_setting
aggregate_failures do
expect(setting.api_url).to eq("https://gitlab.example.com/api/0/projects/sentry-org/sentry-project")
expect(setting.project_name).to eq("Sentry Project")
expect(setting.organization_name).to eq("Sentry Org")
end
end
it 'restores external pull requests' do
external_pr = @project.external_pull_requests.last
aggregate_failures do
expect(external_pr.pull_request_iid).to eq(4)
expect(external_pr.source_branch).to eq("feature")
expect(external_pr.target_branch).to eq("master")
expect(external_pr.status).to eq("open")
end
end
it 'restores pipeline schedules' do
pipeline_schedule = @project.pipeline_schedules.last
aggregate_failures do
expect(pipeline_schedule.description).to eq('Schedule Description')
expect(pipeline_schedule.ref).to eq('master')
expect(pipeline_schedule.cron).to eq('0 4 * * 0')
expect(pipeline_schedule.cron_timezone).to eq('UTC')
expect(pipeline_schedule.active).to eq(true)
end
end
it 'restores releases with links' do
release = @project.releases.last
link = release.links.last
aggregate_failures do
expect(release.tag).to eq('release-1.1')
expect(release.description).to eq('Some release notes')
expect(release.name).to eq('release-1.1')
expect(release.sha).to eq('901de3a8bd5573f4a049b1457d28bc1592ba6bf9')
expect(release.released_at).to eq('2019-12-26T10:17:14.615Z')
expect(link.url).to eq('http://localhost/namespace6/project6/-/jobs/140463678/artifacts/download')
expect(link.name).to eq('release-1.1.dmg')
end
end
context 'Merge requests' do
it 'always has the new project as a target' do
expect(MergeRequest.find_by_title('MR1').target_project).to eq(@project)
end
it 'has the same source project as originally if source/target are the same' do
expect(MergeRequest.find_by_title('MR1').source_project).to eq(@project)
end
it 'has the new project as target if source/target differ' do
expect(MergeRequest.find_by_title('MR2').target_project).to eq(@project)
end
it 'has no source if source/target differ' do
expect(MergeRequest.find_by_title('MR2').source_project_id).to be_nil
end
it 'has award emoji' do
award_emoji = MergeRequest.find_by_title('MR1').award_emoji
expect(award_emoji.map(&:name)).to contain_exactly('thumbsup', 'drum')
end
context 'notes' do
it 'has award emoji' do
merge_request_note = match_mr1_note('Sit voluptatibus eveniet architecto quidem')
award_emoji = merge_request_note.award_emoji.first
expect(award_emoji.name).to eq('tada')
end
end
end
context 'tokens are regenerated' do
it 'has new CI trigger tokens' do
expect(Ci::Trigger.where(token: %w[cdbfasdf44a5958c83654733449e585 33a66349b5ad01fc00174af87804e40]))
.to be_empty
end
it 'has a new CI build token' do
expect(Ci::Build.where(token: 'abcd')).to be_empty
end
end
context 'has restored the correct number of records' do
it 'has the correct number of merge requests' do
expect(@project.merge_requests.size).to eq(9)
end
it 'only restores valid triggers' do
expect(@project.triggers.size).to eq(1)
end
it 'has the correct number of pipelines and statuses' do
expect(@project.ci_pipelines.size).to eq(7)
@project.ci_pipelines.order(:id).zip([2, 0, 2, 2, 2, 2, 0])
.each do |(pipeline, expected_status_size)|
expect(pipeline.statuses.size).to eq(expected_status_size)
end
end
end
context 'when restoring hierarchy of pipeline, stages and jobs' do
it 'restores pipelines' do
expect(Ci::Pipeline.all.count).to be 7
end
it 'restores pipeline stages' do
expect(Ci::Stage.all.count).to be 6
end
it 'correctly restores association between stage and a pipeline' do
expect(Ci::Stage.all).to all(have_attributes(pipeline_id: a_value > 0))
end
it 'restores statuses' do
expect(CommitStatus.all.count).to be 10
end
it 'correctly restores association between a stage and a job' do
expect(CommitStatus.all).to all(have_attributes(stage_id: a_value > 0))
end
it 'correctly restores association between a pipeline and a job' do
expect(CommitStatus.all).to all(have_attributes(pipeline_id: a_value > 0))
end
it 'restores a Hash for CommitStatus options' do
expect(CommitStatus.all.map(&:options).compact).to all(be_a(Hash))
end
it 'restores external pull request for the restored pipeline' do
pipeline_with_external_pr = @project.ci_pipelines.find_by(source: 'external_pull_request_event')
expect(pipeline_with_external_pr.external_pull_request).to be_persisted
end
it 'has no import failures' do
expect(@project.import_failures.size).to eq 0
end
end
end
end
shared_examples 'restores group correctly' do |**results|
it 'has group label' do
expect(project.group.labels.size).to eq(results.fetch(:labels, 0))
expect(project.group.labels.where(type: "GroupLabel").where.not(project_id: nil).count).to eq(0)
end
it 'has group milestone' do
expect(project.group.milestones.size).to eq(results.fetch(:milestones, 0))
end
it 'has the correct visibility level' do
# INTERNAL in the `project.json`, group's is PRIVATE
expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
end
end
context 'project.json file access check' do
let(:user) { create(:user) }
let!(:project) { create(:project, :builds_disabled, :issues_disabled, name: 'project', path: 'project') }
let(:project_tree_restorer) do
described_class.new(user: user, shared: shared, project: project)
end
let(:restored_project_json) { project_tree_restorer.restore }
it 'does not read a symlink' do
Dir.mktmpdir do |tmpdir|
setup_symlink(tmpdir, 'project.json')
allow(shared).to receive(:export_path).and_call_original
expect(project_tree_restorer.restore).to eq(false)
expect(shared.errors).to include('invalid import format')
end
end
end
context 'Light JSON' do
let(:user) { create(:user) }
let!(:project) { create(:project, :builds_disabled, :issues_disabled, name: 'project', path: 'project') }
let(:project_tree_restorer) { described_class.new(user: user, shared: shared, project: project) }
let(:restored_project_json) { project_tree_restorer.restore }
context 'with a simple project' do
before do
setup_import_export_config('light')
setup_reader(reader)
expect(restored_project_json).to eq(true)
end
it 'issue system note metadata restored successfully' do
note_content = 'created merge request !1 to address this issue'
note = project.issues.first.notes.select { |n| n.note.match(/#{note_content}/)}.first
expect(note.noteable_type).to eq('Issue')
expect(note.system).to eq(true)
expect(note.system_note_metadata.action).to eq('merge')
expect(note.system_note_metadata.commit_count).to be_nil
end
context 'when there is an existing build with build token' do
before do
create(:ci_build, token: 'abcd')
end
it_behaves_like 'restores project successfully',
issues: 1,
labels: 2,
label_with_priorities: 'A project label',
milestones: 1,
first_issue_labels: 1
end
context 'when there is an existing build with build token' do
before do
create(:ci_build, token: 'abcd')
end
it_behaves_like 'restores project successfully',
issues: 1,
labels: 2,
label_with_priorities: 'A project label',
milestones: 1,
first_issue_labels: 1
end
end
context 'multiple pipelines reference the same external pull request' do
before do
setup_import_export_config('multi_pipeline_ref_one_external_pr')
setup_reader(reader)
expect(restored_project_json).to eq(true)
end
it_behaves_like 'restores project successfully',
issues: 0,
labels: 0,
milestones: 0,
ci_pipelines: 2,
external_pull_requests: 1,
import_failures: 0
it 'restores external pull request for the restored pipelines' do
external_pr = project.external_pull_requests.first
project.ci_pipelines.each do |pipeline_with_external_pr|
expect(pipeline_with_external_pr.external_pull_request).to be_persisted
expect(pipeline_with_external_pr.external_pull_request).to eq(external_pr)
end
end
end
context 'when post import action throw non-retriable exception' do
let(:exception) { StandardError.new('post_import_error') }
before do
setup_import_export_config('light')
setup_reader(reader)
expect(project)
.to receive(:merge_requests)
.and_raise(exception)
end
it 'report post import error' do
expect(restored_project_json).to eq(false)
expect(shared.errors).to include('post_import_error')
end
end
context 'when post import action throw retriable exception one time' do
let(:exception) { GRPC::DeadlineExceeded.new }
before do
setup_import_export_config('light')
setup_reader(reader)
expect(project)
.to receive(:merge_requests)
.and_raise(exception)
expect(project)
.to receive(:merge_requests)
.and_call_original
expect(restored_project_json).to eq(true)
end
it_behaves_like 'restores project successfully',
issues: 1,
labels: 2,
label_with_priorities: 'A project label',
milestones: 1,
first_issue_labels: 1,
import_failures: 1
it 'records the failures in the database' do
import_failure = ImportFailure.last
expect(import_failure.project_id).to eq(project.id)
expect(import_failure.relation_key).to be_nil
expect(import_failure.relation_index).to be_nil
expect(import_failure.exception_class).to eq('GRPC::DeadlineExceeded')
expect(import_failure.exception_message).to be_present
expect(import_failure.correlation_id_value).not_to be_empty
expect(import_failure.created_at).to be_present
end
end
context 'when the project has overridden params in import data' do
before do
setup_import_export_config('light')
setup_reader(reader)
end
it 'handles string versions of visibility_level' do
# Project needs to be in a group for visibility level comparison
# to happen
group = create(:group)
project.group = group
project.create_import_data(data: { override_params: { visibility_level: Gitlab::VisibilityLevel::INTERNAL.to_s } })
expect(restored_project_json).to eq(true)
expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL)
end
it 'overwrites the params stored in the JSON' do
project.create_import_data(data: { override_params: { description: "Overridden" } })
expect(restored_project_json).to eq(true)
expect(project.description).to eq("Overridden")
end
it 'does not allow setting params that are excluded from import_export settings' do
original_value = project.lfs_enabled?
project.create_import_data(data: { override_params: { lfs_enabled: !original_value } })
expect(restored_project_json).to eq(true)
expect(project.lfs_enabled).to eq(original_value)
end
it 'overrides project feature access levels' do
access_level_keys = ProjectFeature.available_features.map { |feature| ProjectFeature.access_level_attribute(feature) }
disabled_access_levels = Hash[access_level_keys.collect { |item| [item, 'disabled'] }]
project.create_import_data(data: { override_params: disabled_access_levels })
expect(restored_project_json).to eq(true)
aggregate_failures do
access_level_keys.each do |key|
expect(project.public_send(key)).to eq(ProjectFeature::DISABLED)
end
end
end
end
context 'with a project that has a group' do
let!(:project) do
create(:project,
:builds_disabled,
:issues_disabled,
name: 'project',
path: 'project',
group: create(:group, visibility_level: Gitlab::VisibilityLevel::PRIVATE))
end
before do
setup_import_export_config('group')
setup_reader(reader)
expect(restored_project_json).to eq(true)
end
it_behaves_like 'restores project successfully',
issues: 3,
labels: 2,
label_with_priorities: 'A project label',
milestones: 2,
first_issue_labels: 1
it_behaves_like 'restores group correctly',
labels: 0,
milestones: 0,
first_issue_labels: 1
it 'restores issue states' do
expect(project.issues.with_state(:closed).count).to eq(1)
expect(project.issues.with_state(:opened).count).to eq(2)
end
end
context 'with existing group models' do
let!(:project) do
create(:project,
:builds_disabled,
:issues_disabled,
name: 'project',
path: 'project',
group: create(:group))
end
before do
setup_import_export_config('light')
setup_reader(reader)
end
it 'imports labels' do
create(:group_label, name: 'Another label', group: project.group)
expect_any_instance_of(Gitlab::ImportExport::Shared).not_to receive(:error)
expect(restored_project_json).to eq(true)
expect(project.labels.count).to eq(1)
end
it 'imports milestones' do
create(:milestone, name: 'A milestone', group: project.group)
expect_any_instance_of(Gitlab::ImportExport::Shared).not_to receive(:error)
expect(restored_project_json).to eq(true)
expect(project.group.milestones.count).to eq(1)
expect(project.milestones.count).to eq(0)
end
end
context 'with clashing milestones on IID' do
let!(:project) do
create(:project,
:builds_disabled,
:issues_disabled,
name: 'project',
path: 'project',
group: create(:group))
end
before do
setup_import_export_config('milestone-iid')
setup_reader(reader)
end
it 'preserves the project milestone IID' do
expect_any_instance_of(Gitlab::ImportExport::Shared).not_to receive(:error)
expect(restored_project_json).to eq(true)
expect(project.milestones.count).to eq(2)
expect(Milestone.find_by_title('Another milestone').iid).to eq(1)
expect(Milestone.find_by_title('Group-level milestone').iid).to eq(2)
end
end
context 'with external authorization classification labels' do
before do
setup_import_export_config('light')
setup_reader(reader)
end
it 'converts empty external classification authorization labels to nil' do
project.create_import_data(data: { override_params: { external_authorization_classification_label: "" } })
expect(restored_project_json).to eq(true)
expect(project.external_authorization_classification_label).to be_nil
end
it 'preserves valid external classification authorization labels' do
project.create_import_data(data: { override_params: { external_authorization_classification_label: "foobar" } })
expect(restored_project_json).to eq(true)
expect(project.external_authorization_classification_label).to eq("foobar")
end
end
end
context 'Minimal JSON' do
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:tree_hash) { { 'visibility_level' => visibility } }
let(:restorer) do
described_class.new(user: user, shared: shared, project: project)
end
before do
allow_any_instance_of(Gitlab::ImportExport::JSON::LegacyReader::File).to receive(:exist?).and_return(true)
allow_any_instance_of(Gitlab::ImportExport::JSON::NdjsonReader).to receive(:exist?).and_return(false)
allow_any_instance_of(Gitlab::ImportExport::JSON::LegacyReader::File).to receive(:tree_hash) { tree_hash }
end
context 'no group visibility' do
let(:visibility) { Gitlab::VisibilityLevel::PRIVATE }
it 'uses the project visibility' do
expect(restorer.restore).to eq(true)
expect(restorer.project.visibility_level).to eq(visibility)
end
end
context 'with restricted internal visibility' do
describe 'internal project' do
let(:visibility) { Gitlab::VisibilityLevel::INTERNAL }
it 'uses private visibility' do
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::INTERNAL])
expect(restorer.restore).to eq(true)
expect(restorer.project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
end
end
end
context 'with group visibility' do
before do
group = create(:group, visibility_level: group_visibility)
project.update(group: group)
end
context 'private group visibility' do
let(:group_visibility) { Gitlab::VisibilityLevel::PRIVATE }
let(:visibility) { Gitlab::VisibilityLevel::PUBLIC }
it 'uses the group visibility' do
expect(restorer.restore).to eq(true)
expect(restorer.project.visibility_level).to eq(group_visibility)
end
end
context 'public group visibility' do
let(:group_visibility) { Gitlab::VisibilityLevel::PUBLIC }
let(:visibility) { Gitlab::VisibilityLevel::PRIVATE }
it 'uses the project visibility' do
expect(restorer.restore).to eq(true)
expect(restorer.project.visibility_level).to eq(visibility)
end
end
context 'internal group visibility' do
let(:group_visibility) { Gitlab::VisibilityLevel::INTERNAL }
let(:visibility) { Gitlab::VisibilityLevel::PUBLIC }
it 'uses the group visibility' do
expect(restorer.restore).to eq(true)
expect(restorer.project.visibility_level).to eq(group_visibility)
end
context 'with restricted internal visibility' do
it 'sets private visibility' do
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::INTERNAL])
expect(restorer.restore).to eq(true)
expect(restorer.project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
end
end
end
end
context 'with project members' do
let(:user) { create(:user, :admin) }
let(:user2) { create(:user) }
let(:project_members) do
[
{
"id" => 2,
"access_level" => 40,
"source_type" => "Project",
"notification_level" => 3,
"user" => {
"id" => user2.id,
"email" => user2.email,
"username" => 'test'
}
}
]
end
let(:tree_hash) { { 'project_members' => project_members } }
before do
project.add_maintainer(user)
end
it 'restores project members' do
restorer.restore
expect(project.members.map(&:user)).to contain_exactly(user, user2)
end
end
end
context 'JSON with invalid records' do
subject(:restored_project_json) { project_tree_restorer.restore }
let(:user) { create(:user) }
let!(:project) { create(:project, :builds_disabled, :issues_disabled, name: 'project', path: 'project') }
let(:project_tree_restorer) { described_class.new(user: user, shared: shared, project: project) }
before do
setup_import_export_config('with_invalid_records')
setup_reader(reader)
subject
end
context 'when failures occur because a relation fails to be processed' do
it_behaves_like 'restores project successfully',
issues: 0,
labels: 0,
label_with_priorities: nil,
milestones: 1,
first_issue_labels: 0,
import_failures: 1
it 'records the failures in the database' do
import_failure = ImportFailure.last
expect(import_failure.project_id).to eq(project.id)
expect(import_failure.relation_key).to eq('milestones')
expect(import_failure.relation_index).to be_present
expect(import_failure.exception_class).to eq('ActiveRecord::RecordInvalid')
expect(import_failure.exception_message).to be_present
expect(import_failure.correlation_id_value).not_to be_empty
expect(import_failure.created_at).to be_present
end
end
end
context 'JSON with design management data' do
let_it_be(:user) { create(:admin, email: 'user_1@gitlabexample.com') }
let_it_be(:second_user) { create(:user, email: 'user_2@gitlabexample.com') }
let_it_be(:project) do
create(:project, :builds_disabled, :issues_disabled,
{ name: 'project', path: 'project' })
end
let(:shared) { project.import_export_shared }
let(:project_tree_restorer) { described_class.new(user: user, shared: shared, project: project) }
subject(:restored_project_json) { project_tree_restorer.restore }
before do
setup_import_export_config('designs')
restored_project_json
end
it_behaves_like 'restores project successfully', issues: 2
it 'restores project associations correctly' do
expect(project.designs.size).to eq(7)
end
describe 'restores issue associations correctly' do
let(:issue) { project.issues.offset(index).first }
where(:index, :design_filenames, :version_shas, :events, :author_emails) do
0 | %w[chirrido3.jpg jonathan_richman.jpg mariavontrap.jpeg] | %w[27702d08f5ee021ae938737f84e8fe7c38599e85 9358d1bac8ff300d3d2597adaa2572a20f7f8703 e1a4a501bcb42f291f84e5d04c8f927821542fb6] | %w[creation creation creation modification modification deletion] | %w[user_1@gitlabexample.com user_1@gitlabexample.com user_2@gitlabexample.com]
1 | ['1 (1).jpeg', '2099743.jpg', 'a screenshot (1).jpg', 'chirrido3.jpg'] | %w[73f871b4c8c1d65c62c460635e023179fb53abc4 8587e78ab6bda3bc820a9f014c3be4a21ad4fcc8 c9b5f067f3e892122a4b12b0a25a8089192f3ac8] | %w[creation creation creation creation modification] | %w[user_1@gitlabexample.com user_2@gitlabexample.com user_2@gitlabexample.com]
end
with_them do
it do
expect(issue.designs.pluck(:filename)).to contain_exactly(*design_filenames)
expect(issue.design_versions.pluck(:sha)).to contain_exactly(*version_shas)
expect(issue.design_versions.flat_map(&:actions).map(&:event)).to contain_exactly(*events)
expect(issue.design_versions.map(&:author).map(&:email)).to contain_exactly(*author_emails)
end
end
end
describe 'restores design version associations correctly' do
let(:project_designs) { project.designs.reorder(:filename, :issue_id) }
let(:design) { project_designs.offset(index).first }
where(:index, :version_shas) do
0 | %w[73f871b4c8c1d65c62c460635e023179fb53abc4 c9b5f067f3e892122a4b12b0a25a8089192f3ac8]
1 | %w[73f871b4c8c1d65c62c460635e023179fb53abc4]
2 | %w[c9b5f067f3e892122a4b12b0a25a8089192f3ac8]
3 | %w[27702d08f5ee021ae938737f84e8fe7c38599e85 9358d1bac8ff300d3d2597adaa2572a20f7f8703 e1a4a501bcb42f291f84e5d04c8f927821542fb6]
4 | %w[8587e78ab6bda3bc820a9f014c3be4a21ad4fcc8]
5 | %w[27702d08f5ee021ae938737f84e8fe7c38599e85 e1a4a501bcb42f291f84e5d04c8f927821542fb6]
6 | %w[27702d08f5ee021ae938737f84e8fe7c38599e85]
end
with_them do
it do
expect(design.versions.pluck(:sha)).to contain_exactly(*version_shas)
end
end
end
end
end
context 'enable ndjson import' do
it_behaves_like 'project tree restorer work properly', :legacy_reader, true
it_behaves_like 'project tree restorer work properly', :ndjson_reader, true
end
context 'disable ndjson import' do
it_behaves_like 'project tree restorer work properly', :legacy_reader, false
end
end