mirror of
https://gitlab.com/gitlab-org/gitlab-foss.git
synced 2025-07-29 12:00:32 +00:00
359 lines
11 KiB
Ruby
359 lines
11 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'spec_helper'
|
|
|
|
RSpec.describe BulkImport, type: :model, feature_category: :importers do
|
|
using RSpec::Parameterized::TableSyntax
|
|
|
|
let_it_be(:created_bulk_import) { create(:bulk_import, :created, updated_at: 2.hours.ago) }
|
|
let_it_be(:started_bulk_import) { create(:bulk_import, :started, updated_at: 3.hours.ago) }
|
|
let_it_be(:finished_bulk_import) { create(:bulk_import, :finished, updated_at: 1.hour.ago) }
|
|
let_it_be(:failed_bulk_import) { create(:bulk_import, :failed) }
|
|
let_it_be(:stale_created_bulk_import) { create(:bulk_import, :created, updated_at: 3.days.ago) }
|
|
let_it_be(:stale_started_bulk_import) { create(:bulk_import, :started, updated_at: 2.days.ago) }
|
|
|
|
describe 'associations' do
|
|
it { is_expected.to belong_to(:user).required }
|
|
it { is_expected.to belong_to(:organization) }
|
|
it { is_expected.to have_one(:configuration) }
|
|
it { is_expected.to have_many(:entities) }
|
|
end
|
|
|
|
describe 'validations' do
|
|
it { is_expected.to validate_presence_of(:source_type) }
|
|
it { is_expected.to validate_presence_of(:status) }
|
|
|
|
it { is_expected.to define_enum_for(:source_type).with_values(%i[gitlab]) }
|
|
end
|
|
|
|
describe 'scopes' do
|
|
describe '.stale' do
|
|
subject { described_class.stale }
|
|
|
|
it { is_expected.to contain_exactly(stale_created_bulk_import, stale_started_bulk_import) }
|
|
end
|
|
|
|
describe '.order_by_updated_at_and_id' do
|
|
subject { described_class.order_by_updated_at_and_id(:desc) }
|
|
|
|
it 'sorts by given direction' do
|
|
is_expected.to eq([
|
|
failed_bulk_import,
|
|
finished_bulk_import,
|
|
created_bulk_import,
|
|
started_bulk_import,
|
|
stale_started_bulk_import,
|
|
stale_created_bulk_import
|
|
])
|
|
end
|
|
end
|
|
|
|
describe '.with_configuration' do
|
|
it 'includes configuration association' do
|
|
imports = described_class.with_configuration
|
|
|
|
expect(imports.first.association_cached?(:configuration)).to be(true)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '.all_human_statuses' do
|
|
it 'returns all human readable entity statuses' do
|
|
expect(described_class.all_human_statuses)
|
|
.to contain_exactly('created', 'started', 'finished', 'failed', 'timeout', 'canceled')
|
|
end
|
|
end
|
|
|
|
describe '.min_gl_version_for_project' do
|
|
it { expect(described_class.min_gl_version_for_project_migration).to be_a(Gitlab::VersionInfo) }
|
|
it { expect(described_class.min_gl_version_for_project_migration.to_s).to eq('14.4.0') }
|
|
end
|
|
|
|
describe '#completed?' do
|
|
it { expect(described_class.new(status: -2)).to be_completed }
|
|
it { expect(described_class.new(status: -1)).to be_completed }
|
|
it { expect(described_class.new(status: 0)).not_to be_completed }
|
|
it { expect(described_class.new(status: 1)).not_to be_completed }
|
|
it { expect(described_class.new(status: 2)).to be_completed }
|
|
it { expect(described_class.new(status: 3)).to be_completed }
|
|
end
|
|
|
|
describe '#source_version_info' do
|
|
it 'returns source_version as Gitlab::VersionInfo' do
|
|
bulk_import = build(:bulk_import, source_version: '9.13.2')
|
|
|
|
expect(bulk_import.source_version_info).to be_a(Gitlab::VersionInfo)
|
|
expect(bulk_import.source_version_info.to_s).to eq(bulk_import.source_version)
|
|
end
|
|
end
|
|
|
|
describe '#update_has_failures' do
|
|
let(:import) { create(:bulk_import, :started) }
|
|
let(:entity) { create(:bulk_import_entity, bulk_import: import) }
|
|
|
|
context 'when entity has failures' do
|
|
it 'sets has_failures flag to true' do
|
|
expect(import.has_failures).to eq(false)
|
|
|
|
entity.update!(has_failures: true)
|
|
import.fail_op!
|
|
|
|
expect(import.has_failures).to eq(true)
|
|
end
|
|
end
|
|
|
|
context 'when entity does not have failures' do
|
|
it 'sets has_failures flag to false' do
|
|
expect(import.has_failures).to eq(false)
|
|
|
|
entity.update!(has_failures: false)
|
|
import.fail_op!
|
|
|
|
expect(import.has_failures).to eq(false)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#supports_batched_export?' do
|
|
context 'when source version is greater than min supported version for batched migrations' do
|
|
it 'returns true' do
|
|
bulk_import = build(:bulk_import, source_version: '16.2.0')
|
|
|
|
expect(bulk_import.supports_batched_export?).to eq(true)
|
|
end
|
|
end
|
|
|
|
context 'when source version is less than min supported version for batched migrations' do
|
|
it 'returns false' do
|
|
bulk_import = build(:bulk_import, source_version: '15.5.0')
|
|
|
|
expect(bulk_import.supports_batched_export?).to eq(false)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'import canceling' do
|
|
let(:import) { create(:bulk_import, :started) }
|
|
|
|
it 'marks import as canceled' do
|
|
expect(import.canceled?).to eq(false)
|
|
|
|
import.cancel!
|
|
|
|
expect(import.canceled?).to eq(true)
|
|
end
|
|
|
|
context 'when import has entities' do
|
|
it 'marks entities as canceled' do
|
|
entity = create(:bulk_import_entity, bulk_import: import)
|
|
|
|
expect(entity.canceled?).to eq(false)
|
|
|
|
import.cancel!
|
|
|
|
expect(entity.reload.canceled?).to eq(true)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'completion notification trigger' do
|
|
RSpec::Matchers.define :send_completion_notification do
|
|
def supports_block_expectations?
|
|
true
|
|
end
|
|
|
|
match(notify_expectation_failures: true) do |proc|
|
|
expect(Notify).to receive(:bulk_import_complete).with(import.user.id, import.id).and_call_original
|
|
|
|
proc.call
|
|
true
|
|
end
|
|
|
|
match_when_negated(notify_expectation_failures: true) do |proc|
|
|
expect(Notify).not_to receive(:bulk_import_complete)
|
|
|
|
proc.call
|
|
true
|
|
end
|
|
end
|
|
|
|
subject(:import) { create(:bulk_import, :started) }
|
|
|
|
let(:non_triggering_events) do
|
|
import.status_paths.events - %i[finish cleanup_stale fail_op]
|
|
end
|
|
|
|
it { expect { import.finish! }.to send_completion_notification }
|
|
it { expect { import.fail_op! }.to send_completion_notification }
|
|
it { expect { import.cleanup_stale! }.to send_completion_notification }
|
|
|
|
it "does not email after non-completing events" do
|
|
non_triggering_events.each do |event|
|
|
expect { import.send(:"#{event}!") }.not_to send_completion_notification
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#destination_group_roots' do
|
|
let_it_be(:import) { create(:bulk_import, :started) }
|
|
|
|
let_it_be(:project_namespace) { create(:group) }
|
|
let_it_be(:project) { create(:project, namespace: project_namespace) }
|
|
let_it_be(:root_project_entity) do
|
|
create(:bulk_import_entity, :project_entity, project: project, bulk_import: import)
|
|
end
|
|
|
|
let_it_be(:top_level_group) { create(:group) }
|
|
let_it_be(:root_group_entity) do
|
|
create(:bulk_import_entity, :group_entity, group: top_level_group, bulk_import: import)
|
|
end
|
|
|
|
let_it_be(:child_group_entity) do
|
|
create(:bulk_import_entity, parent: root_group_entity, bulk_import: import)
|
|
end
|
|
|
|
it 'returns the topmost group nodes of the import entity tree' do
|
|
expect(import.destination_group_roots).to match_array([project_namespace, top_level_group])
|
|
end
|
|
end
|
|
|
|
describe '#source_url' do
|
|
it 'returns migration source url via configuration' do
|
|
import = create(:bulk_import, :with_configuration)
|
|
|
|
expect(import.source_url).to eq(import.configuration.url)
|
|
end
|
|
|
|
context 'when configuration is missing' do
|
|
it 'returns nil' do
|
|
import = create(:bulk_import)
|
|
|
|
expect(import.source_url).to be_nil
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#source_equals_destination?' do
|
|
subject(:bulk_import) do
|
|
build_stubbed(:bulk_import,
|
|
configuration: build_stubbed(:bulk_import_configuration, url: source_url)
|
|
)
|
|
end
|
|
|
|
before do
|
|
allow(Settings.gitlab).to receive(:host).and_return('gitlab.example')
|
|
end
|
|
|
|
where(:source_url, :value) do
|
|
'https://gitlab.example' | true
|
|
'https://gitlab.example:443' | true
|
|
'https://gitlab.example/' | true
|
|
'https://gitlab.example/dir' | true
|
|
'http://gitlab.example' | true
|
|
'https://gitlab.example2' | false
|
|
'https://subdomain.example' | false
|
|
'https://subdomain.gitlab.example' | false
|
|
'http://192.168.1.1' | false
|
|
end
|
|
|
|
with_them do
|
|
it { expect(bulk_import.source_equals_destination?).to eq(value) }
|
|
end
|
|
end
|
|
|
|
describe '#namespaces_with_unassigned_placeholders' do
|
|
let_it_be(:group) { create(:group) }
|
|
let_it_be(:entity) do
|
|
create(:bulk_import_entity, :group_entity, bulk_import: finished_bulk_import, group: group)
|
|
end
|
|
|
|
before do
|
|
create_list(:import_source_user, 5, :completed, namespace: group)
|
|
end
|
|
|
|
context 'when all placeholders have been assigned' do
|
|
it { expect(finished_bulk_import.namespaces_with_unassigned_placeholders).to be_empty }
|
|
end
|
|
|
|
context 'when some placeholders have not been assigned' do
|
|
before do
|
|
create(:import_source_user, :pending_reassignment, namespace: group)
|
|
end
|
|
|
|
it { expect(finished_bulk_import.namespaces_with_unassigned_placeholders).to include(group) }
|
|
end
|
|
end
|
|
|
|
describe '#schedule_configuration_purge' do
|
|
subject(:schedule_purge) { bulk_import.schedule_configuration_purge }
|
|
|
|
context 'when configuration exists' do
|
|
let_it_be(:bulk_import) { create(:bulk_import, :with_configuration) }
|
|
let(:configuration_id) { bulk_import.configuration.id }
|
|
let(:delay) { 24.hours }
|
|
|
|
before do
|
|
allow(bulk_import).to receive(:run_after_commit).and_yield
|
|
stub_const('BulkImport::PURGE_CONFIGURATION_DELAY', delay)
|
|
end
|
|
|
|
it 'schedules purge worker with default delay' do
|
|
expect(Import::BulkImports::ConfigurationPurgeWorker).to receive(:perform_in).with(delay, configuration_id)
|
|
|
|
schedule_purge
|
|
end
|
|
end
|
|
|
|
context 'when configuration does not exist' do
|
|
let_it_be(:bulk_import) { create(:bulk_import) }
|
|
|
|
it 'does not schedule any job' do
|
|
expect(Import::BulkImports::ConfigurationPurgeWorker).not_to receive(:perform_in)
|
|
|
|
schedule_purge
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'state-machine transitions for configuration purging' do
|
|
RSpec::Matchers.define :schedule_configuration_purge do
|
|
def supports_block_expectations?
|
|
true
|
|
end
|
|
|
|
match(notify_expectation_failures: true) do |proc|
|
|
expect_next_instance_of(described_class) do |instance|
|
|
expect(instance).to receive(:schedule_configuration_purge).and_call_original
|
|
end
|
|
|
|
proc.call
|
|
true
|
|
end
|
|
|
|
match_when_negated(notify_expectation_failures: true) do |proc|
|
|
expect_next_instance_of(described_class) do |instance|
|
|
expect(instance).not_to receive(:schedule_configuration_purge)
|
|
end
|
|
|
|
proc.call
|
|
true
|
|
end
|
|
end
|
|
|
|
context "when bulk import transitions to a completed state" do
|
|
subject(:import) { create(:bulk_import, :started, :with_configuration) }
|
|
|
|
it { expect { import.finish! }.to schedule_configuration_purge }
|
|
it { expect { import.fail_op! }.to schedule_configuration_purge }
|
|
it { expect { import.cleanup_stale! }.to schedule_configuration_purge }
|
|
it { expect { import.cancel }.to schedule_configuration_purge }
|
|
end
|
|
|
|
context "when bulk import transitions to a non-completed state" do
|
|
subject(:import) { create(:bulk_import, :created, :with_configuration) }
|
|
|
|
it { expect { import.start }.not_to schedule_configuration_purge }
|
|
end
|
|
end
|
|
end
|