2020-02-11 04:08:39 -05:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
require 'spec_helper'
|
|
|
|
|
2020-06-24 02:09:01 -04:00
|
|
|
RSpec.describe API::GroupImport do
|
2020-02-11 04:08:39 -05:00
|
|
|
include WorkhorseHelpers
|
|
|
|
|
2021-02-10 19:08:50 -05:00
|
|
|
include_context 'workhorse headers'
|
|
|
|
|
2020-02-11 04:08:39 -05:00
|
|
|
let_it_be(:user) { create(:user) }
|
|
|
|
let_it_be(:group) { create(:group) }
|
2021-06-28 23:07:32 -04:00
|
|
|
|
2020-02-11 04:08:39 -05:00
|
|
|
let(:path) { '/groups/import' }
|
|
|
|
let(:file) { File.join('spec', 'fixtures', 'group_export.tar.gz') }
|
|
|
|
let(:export_path) { "#{Dir.tmpdir}/group_export_spec" }
|
|
|
|
|
|
|
|
before do
|
|
|
|
allow_next_instance_of(Gitlab::ImportExport) do |import_export|
|
|
|
|
expect(import_export).to receive(:storage_path).and_return(export_path)
|
|
|
|
end
|
|
|
|
|
|
|
|
stub_uploads_object_storage(ImportExportUploader)
|
|
|
|
end
|
|
|
|
|
|
|
|
after do
|
|
|
|
FileUtils.rm_rf(export_path, secure: true)
|
|
|
|
end
|
|
|
|
|
|
|
|
describe 'POST /groups/import' do
|
|
|
|
let(:file_upload) { fixture_file_upload(file) }
|
|
|
|
let(:params) do
|
|
|
|
{
|
|
|
|
path: 'test-import-group',
|
|
|
|
name: 'test-import-group',
|
|
|
|
file: fixture_file_upload(file)
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
2020-05-27 17:08:05 -04:00
|
|
|
subject { upload_archive(file_upload, workhorse_headers, params) }
|
2020-02-11 04:08:39 -05:00
|
|
|
|
|
|
|
shared_examples 'when all params are correct' do
|
|
|
|
context 'when user is authorized to create new group' do
|
|
|
|
it 'creates new group and accepts request' do
|
|
|
|
subject
|
|
|
|
|
2020-02-25 07:08:48 -05:00
|
|
|
expect(response).to have_gitlab_http_status(:accepted)
|
2020-02-11 04:08:39 -05:00
|
|
|
end
|
|
|
|
|
2020-02-14 10:09:08 -05:00
|
|
|
it 'creates private group' do
|
|
|
|
expect { subject }.to change { Group.count }.by(1)
|
|
|
|
|
|
|
|
group = Group.find_by(name: 'test-import-group')
|
|
|
|
|
|
|
|
expect(group.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
|
|
|
|
end
|
|
|
|
|
2020-02-11 04:08:39 -05:00
|
|
|
context 'when importing to a parent group' do
|
|
|
|
before do
|
|
|
|
group.add_owner(user)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'creates new group and accepts request' do
|
|
|
|
params[:parent_id] = group.id
|
|
|
|
|
|
|
|
subject
|
|
|
|
|
2020-02-25 07:08:48 -05:00
|
|
|
expect(response).to have_gitlab_http_status(:accepted)
|
2020-02-11 04:08:39 -05:00
|
|
|
expect(group.children.count).to eq(1)
|
|
|
|
end
|
|
|
|
|
2020-02-14 10:09:08 -05:00
|
|
|
context 'when parent group is private or internal' do
|
|
|
|
let(:public_parent_group) { create(:group, :public) }
|
|
|
|
let(:internal_parent_group) { create(:group, :internal) }
|
|
|
|
|
|
|
|
before do
|
|
|
|
public_parent_group.add_owner(user)
|
|
|
|
internal_parent_group.add_owner(user)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'imports public group' do
|
|
|
|
params[:parent_id] = public_parent_group.id
|
|
|
|
|
|
|
|
subject
|
|
|
|
|
2020-02-25 07:08:48 -05:00
|
|
|
expect(response).to have_gitlab_http_status(:accepted)
|
2020-02-14 10:09:08 -05:00
|
|
|
expect(public_parent_group.children.first.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'imports internal group' do
|
|
|
|
params[:parent_id] = internal_parent_group.id
|
|
|
|
|
|
|
|
subject
|
|
|
|
|
2020-02-25 07:08:48 -05:00
|
|
|
expect(response).to have_gitlab_http_status(:accepted)
|
2020-02-14 10:09:08 -05:00
|
|
|
expect(internal_parent_group.children.first.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-02-11 04:08:39 -05:00
|
|
|
context 'when parent group is invalid' do
|
|
|
|
it 'returns 404 and does not create new group' do
|
2020-04-01 11:07:45 -04:00
|
|
|
params[:parent_id] = non_existing_record_id
|
2020-02-11 04:08:39 -05:00
|
|
|
|
|
|
|
expect { subject }.not_to change { Group.count }
|
|
|
|
|
2020-02-25 07:08:48 -05:00
|
|
|
expect(response).to have_gitlab_http_status(:not_found)
|
2020-02-11 04:08:39 -05:00
|
|
|
expect(json_response['message']).to eq('404 Group Not Found')
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when user is not an owner of parent group' do
|
|
|
|
it 'returns 403 Forbidden HTTP status' do
|
|
|
|
params[:parent_id] = create(:group).id
|
|
|
|
|
|
|
|
subject
|
|
|
|
|
2020-02-25 07:08:48 -05:00
|
|
|
expect(response).to have_gitlab_http_status(:forbidden)
|
2020-02-11 04:08:39 -05:00
|
|
|
expect(json_response['message']).to eq('403 Forbidden')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when group creation failed' do
|
|
|
|
before do
|
|
|
|
allow_next_instance_of(Group) do |group|
|
|
|
|
allow(group).to receive(:persisted?).and_return(false)
|
2020-07-16 11:09:38 -04:00
|
|
|
allow(group).to receive(:save).and_return(false)
|
2020-02-11 04:08:39 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns 400 HTTP status' do
|
|
|
|
subject
|
|
|
|
|
2020-02-25 07:08:48 -05:00
|
|
|
expect(response).to have_gitlab_http_status(:bad_request)
|
2020-02-11 04:08:39 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when user is not authorized to create new group' do
|
|
|
|
let(:user) { create(:user, can_create_group: false) }
|
|
|
|
|
|
|
|
it 'forbids the request' do
|
|
|
|
subject
|
|
|
|
|
2020-02-25 07:08:48 -05:00
|
|
|
expect(response).to have_gitlab_http_status(:forbidden)
|
2020-02-11 04:08:39 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
shared_examples 'when some params are missing' do
|
|
|
|
context 'when required params are missing' do
|
|
|
|
shared_examples 'missing parameter' do |params, error_message|
|
|
|
|
it 'returns 400 HTTP status' do
|
|
|
|
params[:file] = file_upload
|
|
|
|
|
|
|
|
expect do
|
2020-05-27 17:08:05 -04:00
|
|
|
upload_archive(file_upload, workhorse_headers, params)
|
2020-02-11 04:08:39 -05:00
|
|
|
end.not_to change { Group.count }.from(1)
|
|
|
|
|
2020-02-25 07:08:48 -05:00
|
|
|
expect(response).to have_gitlab_http_status(:bad_request)
|
2020-02-11 04:08:39 -05:00
|
|
|
expect(json_response['error']).to eq(error_message)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
include_examples 'missing parameter', { name: 'test' }, 'path is missing'
|
|
|
|
include_examples 'missing parameter', { path: 'test' }, 'name is missing'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with object storage disabled' do
|
|
|
|
before do
|
|
|
|
stub_uploads_object_storage(ImportExportUploader, enabled: false)
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'without a file from workhorse' do
|
|
|
|
it 'rejects the request' do
|
2020-05-27 17:08:05 -04:00
|
|
|
upload_archive(nil, workhorse_headers, params)
|
2020-02-11 04:08:39 -05:00
|
|
|
|
|
|
|
expect(response).to have_gitlab_http_status(:bad_request)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'without a workhorse header' do
|
|
|
|
it 'rejects request without a workhorse header' do
|
2020-05-27 17:08:05 -04:00
|
|
|
upload_archive(file_upload, {}, params)
|
2020-02-11 04:08:39 -05:00
|
|
|
|
|
|
|
expect(response).to have_gitlab_http_status(:forbidden)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when params from workhorse are correct' do
|
|
|
|
let(:params) do
|
|
|
|
{
|
|
|
|
path: 'test-import-group',
|
2020-05-27 17:08:05 -04:00
|
|
|
name: 'test-import-group'
|
2020-02-11 04:08:39 -05:00
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
include_examples 'when all params are correct'
|
|
|
|
include_examples 'when some params are missing'
|
|
|
|
end
|
|
|
|
|
|
|
|
it "doesn't attempt to migrate file to object storage" do
|
|
|
|
expect(ObjectStorage::BackgroundMoveWorker).not_to receive(:perform_async)
|
|
|
|
|
|
|
|
subject
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with object storage enabled' do
|
|
|
|
before do
|
|
|
|
stub_uploads_object_storage(ImportExportUploader, enabled: true)
|
|
|
|
|
|
|
|
allow(ImportExportUploader).to receive(:workhorse_upload_path).and_return('/')
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with direct upload enabled' do
|
|
|
|
let(:file_name) { 'group_export.tar.gz' }
|
|
|
|
let!(:fog_connection) do
|
|
|
|
stub_uploads_object_storage(ImportExportUploader, direct_upload: true)
|
|
|
|
end
|
2020-08-10 23:11:00 -04:00
|
|
|
|
2021-04-05 14:09:15 -04:00
|
|
|
# rubocop:disable Rails/SaveBang
|
2020-02-11 04:08:39 -05:00
|
|
|
let(:tmp_object) do
|
|
|
|
fog_connection.directories.new(key: 'uploads').files.create(
|
|
|
|
key: "tmp/uploads/#{file_name}",
|
|
|
|
body: file_upload
|
|
|
|
)
|
|
|
|
end
|
2021-04-05 14:09:15 -04:00
|
|
|
# rubocop:enable Rails/SaveBang
|
2020-08-10 23:11:00 -04:00
|
|
|
|
2020-02-11 04:08:39 -05:00
|
|
|
let(:fog_file) { fog_to_uploaded_file(tmp_object) }
|
|
|
|
let(:params) do
|
|
|
|
{
|
|
|
|
path: 'test-import-group',
|
|
|
|
name: 'test-import-group',
|
2020-05-27 17:08:05 -04:00
|
|
|
file: fog_file
|
2020-02-11 04:08:39 -05:00
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'accepts the request and stores the file' do
|
|
|
|
expect { subject }.to change { Group.count }.by(1)
|
|
|
|
|
|
|
|
expect(response).to have_gitlab_http_status(:accepted)
|
|
|
|
end
|
|
|
|
|
|
|
|
include_examples 'when all params are correct'
|
|
|
|
include_examples 'when some params are missing'
|
|
|
|
end
|
|
|
|
end
|
2020-05-27 17:08:05 -04:00
|
|
|
|
|
|
|
def upload_archive(file, headers = {}, params = {})
|
|
|
|
workhorse_finalize(
|
|
|
|
api('/groups/import', user),
|
|
|
|
method: :post,
|
|
|
|
file_key: :file,
|
|
|
|
params: params.merge(file: file),
|
|
|
|
headers: headers,
|
|
|
|
send_rewritten_field: true
|
|
|
|
)
|
|
|
|
end
|
2020-02-11 04:08:39 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
describe 'POST /groups/import/authorize' do
|
2020-05-27 17:08:05 -04:00
|
|
|
subject { post api('/groups/import/authorize', user), headers: workhorse_headers }
|
2020-02-11 04:08:39 -05:00
|
|
|
|
|
|
|
it 'authorizes importing group with workhorse header' do
|
|
|
|
subject
|
|
|
|
|
|
|
|
expect(response).to have_gitlab_http_status(:ok)
|
2020-12-02 10:09:37 -05:00
|
|
|
expect(response.media_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
|
2020-02-11 04:08:39 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'rejects requests that bypassed gitlab-workhorse' do
|
2020-05-27 17:08:05 -04:00
|
|
|
workhorse_headers.delete(Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER)
|
2020-02-11 04:08:39 -05:00
|
|
|
|
|
|
|
subject
|
|
|
|
|
|
|
|
expect(response).to have_gitlab_http_status(:forbidden)
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when using remote storage' do
|
|
|
|
context 'when direct upload is enabled' do
|
|
|
|
before do
|
|
|
|
stub_uploads_object_storage(ImportExportUploader, enabled: true, direct_upload: true)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'responds with status 200, location of file remote store and object details' do
|
|
|
|
subject
|
|
|
|
|
|
|
|
expect(response).to have_gitlab_http_status(:ok)
|
2020-12-02 10:09:37 -05:00
|
|
|
expect(response.media_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
|
2020-02-11 04:08:39 -05:00
|
|
|
expect(json_response).not_to have_key('TempPath')
|
|
|
|
expect(json_response['RemoteObject']).to have_key('ID')
|
|
|
|
expect(json_response['RemoteObject']).to have_key('GetURL')
|
|
|
|
expect(json_response['RemoteObject']).to have_key('StoreURL')
|
|
|
|
expect(json_response['RemoteObject']).to have_key('DeleteURL')
|
|
|
|
expect(json_response['RemoteObject']).to have_key('MultipartUpload')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when direct upload is disabled' do
|
|
|
|
before do
|
|
|
|
stub_uploads_object_storage(ImportExportUploader, enabled: true, direct_upload: false)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'handles as a local file' do
|
|
|
|
subject
|
|
|
|
|
|
|
|
expect(response).to have_gitlab_http_status(:ok)
|
2020-12-02 10:09:37 -05:00
|
|
|
expect(response.media_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
|
2020-02-11 04:08:39 -05:00
|
|
|
expect(json_response['TempPath']).to eq(ImportExportUploader.workhorse_local_upload_path)
|
|
|
|
expect(json_response['RemoteObject']).to be_nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|