2021-09-30 23:02:18 +05:30
# frozen_string_literal: true
require 'spec_helper'
2023-03-04 22:38:38 +05:30
RSpec . describe API :: BulkImports , feature_category : :importers do
2021-09-30 23:02:18 +05:30
let_it_be ( :user ) { create ( :user ) }
let_it_be ( :import_1 ) { create ( :bulk_import , user : user ) }
let_it_be ( :import_2 ) { create ( :bulk_import , user : user ) }
let_it_be ( :entity_1 ) { create ( :bulk_import_entity , bulk_import : import_1 ) }
let_it_be ( :entity_2 ) { create ( :bulk_import_entity , bulk_import : import_1 ) }
let_it_be ( :entity_3 ) { create ( :bulk_import_entity , bulk_import : import_2 ) }
let_it_be ( :failure_3 ) { create ( :bulk_import_failure , entity : entity_3 ) }
2023-03-17 16:20:25 +05:30
before do
stub_application_setting ( bulk_import_enabled : true )
2023-04-23 21:23:45 +05:30
allow ( :: Gitlab :: ApplicationRateLimiter ) . to receive ( :throttled? ) . and_return ( false )
2023-03-17 16:20:25 +05:30
end
shared_examples 'disabled feature' do
it 'returns 404' do
stub_application_setting ( bulk_import_enabled : false )
request
expect ( response ) . to have_gitlab_http_status ( :not_found )
end
end
2021-09-30 23:02:18 +05:30
describe 'GET /bulk_imports' do
2023-03-17 16:20:25 +05:30
let ( :request ) { get api ( '/bulk_imports' , user ) , params : params }
let ( :params ) { { } }
2021-09-30 23:02:18 +05:30
it 'returns a list of bulk imports authored by the user' do
2023-03-17 16:20:25 +05:30
request
2021-09-30 23:02:18 +05:30
expect ( response ) . to have_gitlab_http_status ( :ok )
expect ( json_response . pluck ( 'id' ) ) . to contain_exactly ( import_1 . id , import_2 . id )
end
2022-06-21 17:19:12 +05:30
context 'sort parameter' do
it 'sorts by created_at descending by default' do
2023-03-17 16:20:25 +05:30
request
2022-06-21 17:19:12 +05:30
expect ( response ) . to have_gitlab_http_status ( :ok )
expect ( json_response . pluck ( 'id' ) ) . to eq ( [ import_2 . id , import_1 . id ] )
end
2023-03-17 16:20:25 +05:30
context 'when explicitly specified' do
context 'when descending' do
let ( :params ) { { sort : 'desc' } }
2022-06-21 17:19:12 +05:30
2023-03-17 16:20:25 +05:30
it 'sorts by created_at descending' do
request
2022-06-21 17:19:12 +05:30
2023-03-17 16:20:25 +05:30
expect ( response ) . to have_gitlab_http_status ( :ok )
expect ( json_response . pluck ( 'id' ) ) . to match_array ( [ import_2 . id , import_1 . id ] )
end
end
2022-06-21 17:19:12 +05:30
2023-03-17 16:20:25 +05:30
context 'when ascending' do
let ( :params ) { { sort : 'asc' } }
it 'sorts by created_at ascending when explicitly specified' do
request
expect ( response ) . to have_gitlab_http_status ( :ok )
expect ( json_response . pluck ( 'id' ) ) . to match_array ( [ import_1 . id , import_2 . id ] )
end
end
2022-06-21 17:19:12 +05:30
end
end
2023-03-17 16:20:25 +05:30
include_examples 'disabled feature'
2021-09-30 23:02:18 +05:30
end
2021-10-27 15:23:28 +05:30
describe 'POST /bulk_imports' do
2023-06-20 00:43:36 +05:30
let_it_be ( :destination_namespace ) { create ( :group ) }
2023-04-23 21:23:45 +05:30
let ( :request ) { post api ( '/bulk_imports' , user ) , params : params }
let ( :destination_param ) { { destination_slug : 'destination_slug' } }
let ( :params ) do
{
configuration : {
url : 'http://gitlab.example' ,
access_token : 'access_token'
} ,
entities : [
{
source_type : 'group_entity' ,
source_full_path : 'full_path' ,
2023-06-20 00:43:36 +05:30
destination_namespace : destination_namespace . path
2023-04-23 21:23:45 +05:30
} . merge ( destination_param )
]
}
end
2023-06-20 00:43:36 +05:30
let ( :source_entity_type ) { BulkImports :: CreateService :: ENTITY_TYPES_MAPPING . fetch ( params [ :entities ] [ 0 ] [ :source_type ] ) }
let ( :source_entity_identifier ) { ERB :: Util . url_encode ( params [ :entities ] [ 0 ] [ :source_full_path ] ) }
2021-11-18 22:05:49 +05:30
before do
allow_next_instance_of ( BulkImports :: Clients :: HTTP ) do | instance |
allow ( instance )
. to receive ( :instance_version )
. and_return (
Gitlab :: VersionInfo . new ( :: BulkImport :: MIN_MAJOR_VERSION , :: BulkImport :: MIN_MINOR_VERSION_FOR_PROJECT ) )
2023-03-04 22:38:38 +05:30
allow ( instance )
. to receive ( :instance_enterprise )
. and_return ( false )
2021-11-18 22:05:49 +05:30
end
2023-06-20 00:43:36 +05:30
stub_request ( :get , " http://gitlab.example/api/v4/ #{ source_entity_type } / #{ source_entity_identifier } /export_relations/status?page=1&per_page=30&private_token=access_token " )
. to_return ( status : 200 , body : " " , headers : { } )
destination_namespace . add_owner ( user )
2021-11-18 22:05:49 +05:30
end
2022-08-27 11:52:29 +05:30
shared_examples 'starting a new migration' do
2023-03-17 16:20:25 +05:30
it 'starts a new migration' do
request
2022-08-27 11:52:29 +05:30
expect ( response ) . to have_gitlab_http_status ( :created )
expect ( json_response [ 'status' ] ) . to eq ( 'created' )
end
2023-03-17 16:20:25 +05:30
describe 'migrate projects flag' do
context 'when true' do
it 'sets true' do
params [ :entities ] [ 0 ] [ :migrate_projects ] = true
request
expect ( user . bulk_imports . last . entities . pluck ( :migrate_projects ) ) . to contain_exactly ( true )
end
end
context 'when false' do
it 'sets false' do
params [ :entities ] [ 0 ] [ :migrate_projects ] = false
request
expect ( user . bulk_imports . last . entities . pluck ( :migrate_projects ) ) . to contain_exactly ( false )
end
end
context 'when unspecified' do
it 'sets true' do
request
expect ( user . bulk_imports . last . entities . pluck ( :migrate_projects ) ) . to contain_exactly ( true )
end
end
end
2022-08-27 11:52:29 +05:30
end
include_examples 'starting a new migration' do
let ( :destination_param ) { { destination_slug : 'destination_slug' } }
end
include_examples 'starting a new migration' do
let ( :destination_param ) { { destination_name : 'destination_name' } }
end
context 'when both destination_name & destination_slug are provided' do
2023-03-17 16:20:25 +05:30
let ( :params ) do
{
2022-08-27 11:52:29 +05:30
configuration : {
url : 'http://gitlab.example' ,
access_token : 'access_token'
} ,
entities : [
{
source_type : 'group_entity' ,
source_full_path : 'full_path' ,
destination_name : 'destination_name' ,
destination_slug : 'destination_slug' ,
destination_namespace : 'destination_namespace'
}
]
}
2023-03-17 16:20:25 +05:30
end
it 'returns a mutually exclusive error' do
request
2022-08-27 11:52:29 +05:30
expect ( response ) . to have_gitlab_http_status ( :bad_request )
expect ( json_response [ 'error' ] ) . to eq ( 'entities[0][destination_slug], entities[0][destination_name] are mutually exclusive' )
end
end
context 'when neither destination_name nor destination_slug is provided' do
2023-03-17 16:20:25 +05:30
let ( :params ) do
{
2022-08-27 11:52:29 +05:30
configuration : {
url : 'http://gitlab.example' ,
access_token : 'access_token'
} ,
entities : [
{
source_type : 'group_entity' ,
source_full_path : 'full_path' ,
2023-06-20 00:43:36 +05:30
destination_namespace : destination_namespace . path
2022-08-27 11:52:29 +05:30
}
]
}
2023-03-17 16:20:25 +05:30
end
it 'returns at_least_one_of error' do
request
2022-08-27 11:52:29 +05:30
expect ( response ) . to have_gitlab_http_status ( :bad_request )
expect ( json_response [ 'error' ] ) . to eq ( 'entities[0][destination_slug], entities[0][destination_name] are missing, at least one parameter must be provided' )
end
2021-10-27 15:23:28 +05:30
end
2023-03-17 16:20:25 +05:30
context 'when the source_full_path is invalid' do
it 'returns invalid error' do
params [ :entities ] [ 0 ] [ :source_full_path ] = 'http://example.com/full_path'
request
expect ( response ) . to have_gitlab_http_status ( :bad_request )
expect ( json_response [ 'error' ] ) . to eq ( " entities[0][source_full_path] must be a relative path and not include protocol, sub-domain, " \
2023-06-20 00:43:36 +05:30
" or domain information. For example, 'source/full/path' not 'https://example.com/source/full/path' " )
2023-03-17 16:20:25 +05:30
end
end
2023-06-20 00:43:36 +05:30
context 'when the destination_namespace does not exist' do
2023-03-17 16:20:25 +05:30
it 'returns invalid error' do
2023-06-20 00:43:36 +05:30
params [ :entities ] [ 0 ] [ :destination_namespace ] = " invalid-destination-namespace "
2023-03-17 16:20:25 +05:30
request
2023-06-20 00:43:36 +05:30
expect ( response ) . to have_gitlab_http_status ( :unprocessable_entity )
expect ( json_response [ 'message' ] ) . to eq ( " Import failed. Destination 'invalid-destination-namespace' is invalid, or you don't have permission. " )
2023-03-17 16:20:25 +05:30
end
end
context 'when the destination_namespace is an empty string' do
it 'accepts the param and starts a new migration' do
params [ :entities ] [ 0 ] [ :destination_namespace ] = ''
request
expect ( response ) . to have_gitlab_http_status ( :created )
expect ( json_response [ 'status' ] ) . to eq ( 'created' )
end
end
context 'when the destination_slug is invalid' do
2023-06-20 00:43:36 +05:30
it 'returns invalid error when restricting special characters is disabled' do
Feature . disable ( :restrict_special_characters_in_namespace_path )
params [ :entities ] [ 0 ] [ :destination_slug ] = 'des?tin?atoi-slugg'
request
expect ( response ) . to have_gitlab_http_status ( :bad_request )
expect ( json_response [ 'error' ] ) . to include ( " entities[0][destination_slug] cannot start with " \
" a non-alphanumeric character except for periods or " \
" underscores, can contain only alphanumeric characters, " \
" periods, and underscores, cannot end with a period or " \
" forward slash, and has no leading or trailing forward " \
" slashes. It can only contain alphanumeric characters, " \
" periods, underscores, and dashes. For example, " \
" 'destination_namespace' not 'destination/namespace' " )
end
it 'returns invalid error when restricting special characters is enabled' do
Feature . enable ( :restrict_special_characters_in_namespace_path )
2023-03-17 16:20:25 +05:30
params [ :entities ] [ 0 ] [ :destination_slug ] = 'des?tin?atoi-slugg'
request
expect ( response ) . to have_gitlab_http_status ( :bad_request )
2023-06-20 00:43:36 +05:30
expect ( json_response [ 'error' ] ) . to include ( " entities[0][destination_slug] must not start or " \
" end with a special character and must not contain " \
" consecutive special characters. It can only contain " \
" alphanumeric characters, periods, underscores, and " \
" dashes. For example, 'destination_namespace' not 'destination/namespace' " )
2023-03-17 16:20:25 +05:30
end
end
2021-10-27 15:23:28 +05:30
context 'when provided url is blocked' do
2023-03-17 16:20:25 +05:30
let ( :params ) do
{
2021-10-27 15:23:28 +05:30
configuration : {
url : 'url' ,
access_token : 'access_token'
} ,
entities : [
source_type : 'group_entity' ,
source_full_path : 'full_path' ,
2022-08-27 11:52:29 +05:30
destination_slug : 'destination_slug' ,
2021-10-27 15:23:28 +05:30
destination_namespace : 'destination_namespace'
]
}
2023-03-17 16:20:25 +05:30
end
2023-06-20 00:43:36 +05:30
it 'returns blocked url message in the error' do
request
expect ( response ) . to have_gitlab_http_status ( :unprocessable_entity )
expect ( json_response [ 'message' ] ) . to include ( " Url is blocked: Only allowed schemes are http, https " )
end
end
context 'when source instance setting is disabled' do
let ( :params ) do
{
configuration : {
url : 'http://gitlab.example' ,
access_token : 'access_token'
} ,
entities : [
source_type : 'group_entity' ,
source_full_path : 'full_path' ,
destination_slug : 'destination_slug' ,
destination_namespace : 'destination_namespace'
]
}
end
2023-03-17 16:20:25 +05:30
it 'returns blocked url error' do
2023-06-20 00:43:36 +05:30
stub_request ( :get , " http://gitlab.example/api/v4/ #{ source_entity_type } / #{ source_entity_identifier } /export_relations/status?page=1&per_page=30&private_token=access_token " )
. to_return ( status : 404 , body : " " , headers : { } )
2023-03-17 16:20:25 +05:30
request
2021-10-27 15:23:28 +05:30
expect ( response ) . to have_gitlab_http_status ( :unprocessable_entity )
2023-06-20 00:43:36 +05:30
expect ( json_response [ 'message' ] ) . to include ( " Group import disabled on source or destination instance. " \
" Ask an administrator to enable it on both instances and try again. " )
2021-10-27 15:23:28 +05:30
end
end
2023-03-17 16:20:25 +05:30
include_examples 'disabled feature'
2023-04-23 21:23:45 +05:30
context 'when request exceeds rate limits' do
it 'prevents user from starting a new migration' do
allow ( :: Gitlab :: ApplicationRateLimiter ) . to receive ( :throttled? ) . and_return ( true )
request
expect ( response ) . to have_gitlab_http_status ( :too_many_requests )
expect ( json_response [ 'message' ] [ 'error' ] ) . to eq ( 'This endpoint has been requested too many times. Try again later.' )
end
end
2021-10-27 15:23:28 +05:30
end
2021-09-30 23:02:18 +05:30
describe 'GET /bulk_imports/entities' do
2023-03-17 16:20:25 +05:30
let ( :request ) { get api ( '/bulk_imports/entities' , user ) }
2021-09-30 23:02:18 +05:30
it 'returns a list of all import entities authored by the user' do
2023-03-17 16:20:25 +05:30
request
2021-09-30 23:02:18 +05:30
expect ( response ) . to have_gitlab_http_status ( :ok )
expect ( json_response . pluck ( 'id' ) ) . to contain_exactly ( entity_1 . id , entity_2 . id , entity_3 . id )
end
2023-03-17 16:20:25 +05:30
include_examples 'disabled feature'
2021-09-30 23:02:18 +05:30
end
describe 'GET /bulk_imports/:id' do
2023-03-17 16:20:25 +05:30
let ( :request ) { get api ( " /bulk_imports/ #{ import_1 . id } " , user ) }
2021-09-30 23:02:18 +05:30
it 'returns specified bulk import' do
2023-03-17 16:20:25 +05:30
request
2021-09-30 23:02:18 +05:30
expect ( response ) . to have_gitlab_http_status ( :ok )
expect ( json_response [ 'id' ] ) . to eq ( import_1 . id )
end
2023-03-17 16:20:25 +05:30
include_examples 'disabled feature'
2021-09-30 23:02:18 +05:30
end
describe 'GET /bulk_imports/:id/entities' do
2023-03-17 16:20:25 +05:30
let ( :request ) { get api ( " /bulk_imports/ #{ import_2 . id } /entities " , user ) }
2021-09-30 23:02:18 +05:30
it 'returns specified bulk import entities with failures' do
2023-03-17 16:20:25 +05:30
request
2021-09-30 23:02:18 +05:30
expect ( response ) . to have_gitlab_http_status ( :ok )
expect ( json_response . pluck ( 'id' ) ) . to contain_exactly ( entity_3 . id )
expect ( json_response . first [ 'failures' ] . first [ 'exception_class' ] ) . to eq ( failure_3 . exception_class )
end
2023-03-17 16:20:25 +05:30
include_examples 'disabled feature'
2021-09-30 23:02:18 +05:30
end
describe 'GET /bulk_imports/:id/entities/:entity_id' do
2023-03-17 16:20:25 +05:30
let ( :request ) { get api ( " /bulk_imports/ #{ import_1 . id } /entities/ #{ entity_2 . id } " , user ) }
2021-09-30 23:02:18 +05:30
it 'returns specified bulk import entity' do
2023-03-17 16:20:25 +05:30
request
2021-09-30 23:02:18 +05:30
expect ( response ) . to have_gitlab_http_status ( :ok )
expect ( json_response [ 'id' ] ) . to eq ( entity_2 . id )
end
2023-03-17 16:20:25 +05:30
include_examples 'disabled feature'
2021-09-30 23:02:18 +05:30
end
context 'when user is unauthenticated' do
it 'returns 401' do
get api ( '/bulk_imports' , nil )
expect ( response ) . to have_gitlab_http_status ( :unauthorized )
end
end
end