Commit 2dc03ca2 authored by Michael Leopard's avatar Michael Leopard

Creating Bitbucket Server import API

Adding integration tests for the new endpoint
Adding CHANGELOG
Creating new Bitbucket server service to handle endpoint logic
parent 368ba899
...@@ -34,26 +34,18 @@ class Import::BitbucketServerController < Import::BaseController ...@@ -34,26 +34,18 @@ class Import::BitbucketServerController < Import::BaseController
return render json: { errors: _("Project %{project_repo} could not be found") % { project_repo: "#{@project_key}/#{@repo_slug}" } }, status: :unprocessable_entity return render json: { errors: _("Project %{project_repo} could not be found") % { project_repo: "#{@project_key}/#{@repo_slug}" } }, status: :unprocessable_entity
end end
project_name = params[:new_name].presence || repo.name result = Import::BitbucketServerService.new(client, current_user, params).execute(credentials)
namespace_path = params[:new_namespace].presence || current_user.username
target_namespace = find_or_create_namespace(namespace_path, current_user)
if current_user.can?(:create_projects, target_namespace) if result[:status] == :success
project = Gitlab::BitbucketServerImport::ProjectCreator.new(@project_key, @repo_slug, repo, project_name, target_namespace, current_user, credentials).execute render json: ProjectSerializer.new.represent(result[:project], serializer: :import)
if project.persisted?
render json: ProjectSerializer.new.represent(project, serializer: :import)
else
render json: { errors: project_save_error(project) }, status: :unprocessable_entity
end
else else
render json: { errors: _('This namespace has already been taken! Please choose another one.') }, status: :unprocessable_entity render json: { errors: result[:message] }, status: result[:http_status]
end end
end end
def configure def configure
session[personal_access_token_key] = params[:personal_access_token] session[personal_access_token_key] = params[:personal_access_token]
session[bitbucket_server_username_key] = params[:bitbucket_username] session[bitbucket_server_username_key] = params[:bitbucket_server_username]
session[bitbucket_server_url_key] = params[:bitbucket_server_url] session[bitbucket_server_url_key] = params[:bitbucket_server_url]
redirect_to status_import_bitbucket_server_path redirect_to status_import_bitbucket_server_path
...@@ -127,8 +119,8 @@ class Import::BitbucketServerController < Import::BaseController ...@@ -127,8 +119,8 @@ class Import::BitbucketServerController < Import::BaseController
end end
def validate_import_params def validate_import_params
@project_key = params[:project] @project_key = params[:bitbucketServerProject]
@repo_slug = params[:repository] @repo_slug = params[:bitbucketServerRepo]
return render_validation_error('Missing project key') unless @project_key.present? && @repo_slug.present? return render_validation_error('Missing project key') unless @project_key.present? && @repo_slug.present?
return render_validation_error('Missing repository slug') unless @repo_slug.present? return render_validation_error('Missing repository slug') unless @repo_slug.present?
......
# frozen_string_literal: true
module Import
class BitbucketServerService < Import::BaseService
attr_reader :client, :params, :current_user
def execute(credentials)
if blocked_url?
return log_and_return_error("Invalid URL: #{url}", :bad_request)
end
unless authorized?
return log_and_return_error("You don't have permissions to create this project", :unauthorized)
end
unless repo
return log_and_return_error("Project %{project_repo} could not be found" % { project_repo: "#{project_key}/#{repo_slug}" }, :unprocessable_entity)
end
project = create_project(credentials)
if project.persisted?
success(project)
else
log_and_return_error(project_save_error(project), :unprocessable_entity)
end
rescue BitbucketServer::Connection::ConnectionError => e
log_and_return_error("Import failed due to a BitBucket Server error: #{e}", :bad_request)
end
private
def create_project(credentials)
Gitlab::BitbucketServerImport::ProjectCreator.new(
project_key,
repo_slug,
repo,
project_name,
target_namespace,
current_user,
credentials
).execute
end
def repo
@repo ||= client.repo(project_key, repo_slug)
end
def project_name
@project_name ||= params[:new_name].presence || repo.name
end
def namespace_path
@namespace_path ||= params[:new_namespace].presence || current_user.namespace_path
end
def target_namespace
@target_namespace ||= find_or_create_namespace(namespace_path, current_user.namespace_path)
end
def repo_slug
@repo_slug ||= params[:bitbucket_server_repo] || params[:bitbucketServerRepo]
end
def project_key
@project_key ||= params[:bitbucket_server_project] || params[:bitbucketServerProject]
end
def url
@url ||= params[:bitbucket_server_url]
end
def authorized?
can?(current_user, :create_projects, target_namespace)
end
def allow_local_requests?
Gitlab::CurrentSettings.allow_local_requests_from_web_hooks_and_services?
end
def blocked_url?
Gitlab::UrlBlocker.blocked_url?(
url,
{
allow_localhost: allow_local_requests?,
allow_local_network: allow_local_requests?,
schemes: %w(http https)
}
)
end
def log_and_return_error(message, error_type)
log_error(message)
error(_(message), error_type)
end
def log_error(message)
Gitlab::Import::Logger.error(
message: 'Import failed due to a BitBucket Server error',
error: message
)
end
end
end
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
.form-group.row .form-group.row
= label_tag :bitbucket_server_url, 'Username', class: 'col-form-label col-md-2' = label_tag :bitbucket_server_url, 'Username', class: 'col-form-label col-md-2'
.col-md-4 .col-md-4
= text_field_tag :bitbucket_username, '', class: 'form-control gl-mr-3', placeholder: _('username'), size: 40 = text_field_tag :bitbucket_server_username, '', class: 'form-control gl-mr-3', placeholder: _('username'), size: 40
.form-group.row .form-group.row
= label_tag :personal_access_token, 'Password/Personal Access Token', class: 'col-form-label col-md-2' = label_tag :personal_access_token, 'Password/Personal Access Token', class: 'col-form-label col-md-2'
.col-md-4 .col-md-4
......
...@@ -55,7 +55,7 @@ ...@@ -55,7 +55,7 @@
= project.human_import_status_name = project.human_import_status_name
- @repos.each do |repo| - @repos.each do |repo|
%tr{ id: "repo_#{repo.project_key}___#{repo.slug}", data: { project: repo.project_key, repository: repo.slug } } %tr{ id: "repo_#{repo.project_key}___#{repo.slug}", data: { bitbucket_server_project: repo.project_key, bitbucket_server_repo: repo.slug } }
%td %td
= sanitize(link_to(repo.browse_url, repo.browse_url, target: '_blank', rel: 'noopener noreferrer'), attributes: %w(href target rel)) = sanitize(link_to(repo.browse_url, repo.browse_url, target: '_blank', rel: 'noopener noreferrer'), attributes: %w(href target rel))
%td.import-target %td.import-target
......
---
title: 'Resolve Feature proposal: API for import from BitBucket Server'
merge_request: 33097
author:
type: added
...@@ -29,3 +29,41 @@ Example response: ...@@ -29,3 +29,41 @@ Example response:
"full_name": "Administrator / my-repo" "full_name": "Administrator / my-repo"
} }
``` ```
## Import repository from Bitbucket Server
Import your projects from Bitbucket Server to GitLab via the API.
NOTE: **Note:**
The Bitbucket Project Key is only used for finding the repository in Bitbucket.
You must specify a `target_namespace` if you want to import the repository to a GitLab group.
If you do not specify `target_namespace`, the project will import to your personal user namespace.
```plaintext
POST /import/bitbucket_server
```
| Attribute | Type | Required | Description |
|------------|---------|----------|---------------------|
| `bitbucket_server_url` | string | yes | Bitbucket Server URL |
| `bitbucket_server_username` | string | yes | Bitbucket Server Username |
| `personal_access_token` | string | yes | Bitbucket Server personal access token/password |
| `bitbucket_server_project` | string | yes | Bitbucket Project Key |
| `bitbucket_server_repo` | string | yes | Bitbucket Repository Name |
| `new_name` | string | no | New repo name |
| `target_namespace` | string | no | Namespace to import repo into |
```shell
curl --request POST \
--url https://gitlab.example.com/api/v4/import/bitbucket/server \
--header "content-type: application/json" \
--header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" \
--data '{
"bitbucket_server_url": "http://bitbucket.example.com",
"bitbucket_server_username": "root",
"personal_access_token": "Nzk4MDcxODY4MDAyOiP8y410zF3tGAyLnHRv/E0+3xYs",
"bitbucket_server_project": "NEW",
"bitbucket_server_repo": "my-repo"
}'
```
...@@ -156,6 +156,7 @@ module API ...@@ -156,6 +156,7 @@ module API
mount ::API::Groups mount ::API::Groups
mount ::API::GroupContainerRepositories mount ::API::GroupContainerRepositories
mount ::API::GroupVariables mount ::API::GroupVariables
mount ::API::ImportBitbucketServer
mount ::API::ImportGithub mount ::API::ImportGithub
mount ::API::Issues mount ::API::Issues
mount ::API::JobArtifacts mount ::API::JobArtifacts
......
# frozen_string_literal: true
module API
class ImportBitbucketServer < Grape::API::Instance
helpers do
def client
@client ||= BitbucketServer::Client.new(credentials)
end
def credentials
@credentials ||= {
base_uri: params[:bitbucket_server_url],
user: params[:bitbucket_server_username],
password: params[:personal_access_token]
}
end
end
desc 'Import a BitBucket Server repository' do
detail 'This feature was introduced in GitLab 13.2.'
success ::ProjectEntity
end
params do
requires :bitbucket_server_url, type: String, desc: 'Bitbucket Server URL'
requires :bitbucket_server_username, type: String, desc: 'BitBucket Server Username'
requires :personal_access_token, type: String, desc: 'BitBucket Server personal access token/password'
requires :bitbucket_server_project, type: String, desc: 'BitBucket Server Project Key'
requires :bitbucket_server_repo, type: String, desc: 'BitBucket Server Repository Name'
optional :new_name, type: String, desc: 'New repo name'
optional :new_namespace, type: String, desc: 'Namespace to import repo into'
end
post 'import/bitbucket_server' do
result = Import::BitbucketServerService.new(client, current_user, params).execute(credentials)
if result[:status] == :success
present ProjectSerializer.new.represent(result[:project], serializer: :import)
else
render_api_error!({ error: result[:message] }, result[:http_status])
end
end
end
end
...@@ -46,7 +46,7 @@ RSpec.describe Import::BitbucketServerController do ...@@ -46,7 +46,7 @@ RSpec.describe Import::BitbucketServerController do
.to receive(:new).with(project_key, repo_slug, anything, project_name, user.namespace, user, anything) .to receive(:new).with(project_key, repo_slug, anything, project_name, user.namespace, user, anything)
.and_return(double(execute: project)) .and_return(double(execute: project))
post :create, params: { project: project_key, repository: repo_slug }, format: :json post :create, params: { bitbucketServerProject: project_key, bitbucketServerRepo: repo_slug }, format: :json
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
end end
...@@ -59,20 +59,20 @@ RSpec.describe Import::BitbucketServerController do ...@@ -59,20 +59,20 @@ RSpec.describe Import::BitbucketServerController do
.to receive(:new).with(project_key, repo_slug, anything, project_name, user.namespace, user, anything) .to receive(:new).with(project_key, repo_slug, anything, project_name, user.namespace, user, anything)
.and_return(double(execute: project)) .and_return(double(execute: project))
post :create, params: { project: project_key, repository: repo_slug, format: :json } post :create, params: { bitbucketServerProject: project_key, bitbucketServerRepo: repo_slug, format: :json }
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
end end
end end
it 'returns an error when an invalid project key is used' do it 'returns an error when an invalid project key is used' do
post :create, params: { project: 'some&project' } post :create, params: { bitbucket_server_project: 'some&project' }
expect(response).to have_gitlab_http_status(:unprocessable_entity) expect(response).to have_gitlab_http_status(:unprocessable_entity)
end end
it 'returns an error when an invalid repository slug is used' do it 'returns an error when an invalid repository slug is used' do
post :create, params: { project: 'some-project', repository: 'try*this' } post :create, params: { bitbucket_server_project: 'some-project', bitbucket_server_repo: 'try*this' }
expect(response).to have_gitlab_http_status(:unprocessable_entity) expect(response).to have_gitlab_http_status(:unprocessable_entity)
end end
...@@ -80,7 +80,7 @@ RSpec.describe Import::BitbucketServerController do ...@@ -80,7 +80,7 @@ RSpec.describe Import::BitbucketServerController do
it 'returns an error when the project cannot be found' do it 'returns an error when the project cannot be found' do
allow(client).to receive(:repo).with(project_key, repo_slug).and_return(nil) allow(client).to receive(:repo).with(project_key, repo_slug).and_return(nil)
post :create, params: { project: project_key, repository: repo_slug }, format: :json post :create, params: { bitbucket_server_project: project_key, bitbucket_server_repo: repo_slug }, format: :json
expect(response).to have_gitlab_http_status(:unprocessable_entity) expect(response).to have_gitlab_http_status(:unprocessable_entity)
end end
...@@ -90,15 +90,15 @@ RSpec.describe Import::BitbucketServerController do ...@@ -90,15 +90,15 @@ RSpec.describe Import::BitbucketServerController do
.to receive(:new).with(project_key, repo_slug, anything, project_name, user.namespace, user, anything) .to receive(:new).with(project_key, repo_slug, anything, project_name, user.namespace, user, anything)
.and_return(double(execute: build(:project))) .and_return(double(execute: build(:project)))
post :create, params: { project: project_key, repository: repo_slug }, format: :json post :create, params: { bitbucket_server_project: project_key, bitbucket_server_repo: repo_slug }, format: :json
expect(response).to have_gitlab_http_status(:unprocessable_entity) expect(response).to have_gitlab_http_status(:unprocessable_entity)
end end
it "returns an error when the server can't be contacted" do it "returns an error when the server can't be contacted" do
expect(client).to receive(:repo).with(project_key, repo_slug).and_raise(::BitbucketServer::Connection::ConnectionError) allow(client).to receive(:repo).with(project_key, repo_slug).and_return([nil, nil])
post :create, params: { project: project_key, repository: repo_slug }, format: :json post :create, params: { bitbucket_server_project: project_key, bitbucket_server_repo: repo_slug }, format: :json
expect(response).to have_gitlab_http_status(:unprocessable_entity) expect(response).to have_gitlab_http_status(:unprocessable_entity)
end end
...@@ -123,7 +123,9 @@ RSpec.describe Import::BitbucketServerController do ...@@ -123,7 +123,9 @@ RSpec.describe Import::BitbucketServerController do
end end
it 'sets the session variables' do it 'sets the session variables' do
post :configure, params: { personal_access_token: token, bitbucket_username: username, bitbucket_server_url: url } allow(controller).to receive(:allow_local_requests?).and_return(true)
post :configure, params: { personal_access_token: token, bitbucket_server_username: username, bitbucket_server_url: url }
expect(session[:bitbucket_server_url]).to eq(url) expect(session[:bitbucket_server_url]).to eq(url)
expect(session[:bitbucket_server_username]).to eq(username) expect(session[:bitbucket_server_username]).to eq(username)
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe API::ImportBitbucketServer do
let(:base_uri) { "https://test:7990" }
let(:user) { create(:user) }
let(:token) { "asdasd12345" }
let(:secret) { "sekrettt" }
let(:project_key) { 'TES' }
let(:repo_slug) { 'vim' }
let(:repo) { { name: 'vim' } }
describe "POST /import/bitbucket_server" do
context 'with no optional parameters' do
let_it_be(:project) { create(:project) }
let(:client) { double(BitbucketServer::Client) }
before do
Grape::Endpoint.before_each do |endpoint|
allow(endpoint).to receive(:client).and_return(client.as_null_object)
allow(client).to receive(:repo).with(project_key, repo_slug).and_return(double(name: repo_slug))
end
end
after do
Grape::Endpoint.before_each nil
end
it 'returns 201 response when the project is imported successfully' do
allow(Gitlab::BitbucketServerImport::ProjectCreator)
.to receive(:new).with(project_key, repo_slug, anything, repo_slug, user.namespace, user, anything)
.and_return(double(execute: project))
post api("/import/bitbucket_server", user), params: {
bitbucket_server_url: base_uri,
bitbucket_server_username: user,
personal_access_token: token,
bitbucket_server_project: project_key,
bitbucket_server_repo: repo_slug
}
expect(response).to have_gitlab_http_status(:created)
expect(json_response).to be_a Hash
expect(json_response['name']).to eq(project.name)
end
end
context 'with a new project name' do
let_it_be(:project) { create(:project, name: 'new-name') }
let(:client) { instance_double(BitbucketServer::Client) }
before do
Grape::Endpoint.before_each do |endpoint|
allow(endpoint).to receive(:client).and_return(client)
allow(client).to receive(:repo).with(project_key, repo_slug).and_return(double(name: repo_slug))
end
end
after do
Grape::Endpoint.before_each nil
end
it 'returns 201 response when the project is imported successfully with a new project name' do
allow(Gitlab::BitbucketServerImport::ProjectCreator)
.to receive(:new).with(project_key, repo_slug, anything, project.name, user.namespace, user, anything)
.and_return(double(execute: project))
post api("/import/bitbucket_server", user), params: {
bitbucket_server_url: base_uri,
bitbucket_server_username: user,
personal_access_token: token,
bitbucket_server_project: project_key,
bitbucket_server_repo: repo_slug,
new_name: 'new-name'
}
expect(response).to have_gitlab_http_status(:created)
expect(json_response).to be_a Hash
expect(json_response['name']).to eq('new-name')
end
end
context 'with an invalid URL' do
let_it_be(:project) { create(:project, name: 'new-name') }
let(:client) { instance_double(BitbucketServer::Client) }
before do
Grape::Endpoint.before_each do |endpoint|
allow(endpoint).to receive(:client).and_return(client)
allow(client).to receive(:repo).with(project_key, repo_slug).and_return(double(name: repo_slug))
end
end
after do
Grape::Endpoint.before_each nil
end
it 'returns 400 response due to a blcoked URL' do
allow(Gitlab::BitbucketServerImport::ProjectCreator)
.to receive(:new).with(project_key, repo_slug, anything, project.name, user.namespace, user, anything)
.and_return(double(execute: project))
allow(Gitlab::UrlBlocker)
.to receive(:blocked_url?)
.and_return(true)
post api("/import/bitbucket_server", user), params: {
bitbucket_server_url: base_uri,
bitbucket_server_username: user,
personal_access_token: token,
bitbucket_server_project: project_key,
bitbucket_server_repo: repo_slug,
new_name: 'new-name'
}
expect(response).to have_gitlab_http_status(:bad_request)
end
end
context 'with a new namespace' do
let(:bitbucket_client) { instance_double(BitbucketServer::Client) }
before do
Grape::Endpoint.before_each do |endpoint|
allow(endpoint).to receive(:client).and_return(bitbucket_client)
repo = double(name: repo_slug, full_path: "/other-namespace/#{repo_slug}")
allow(bitbucket_client).to receive(:repo).with(project_key, repo_slug).and_return(repo)
end
end
after do
Grape::Endpoint.before_each nil
end
it 'returns 201 response when the project is imported successfully to a new namespace' do
allow(Gitlab::BitbucketServerImport::ProjectCreator)
.to receive(:new).with(project_key, repo_slug, anything, repo_slug, an_instance_of(Group), user, anything)
.and_return(double(execute: create(:project, name: repo_slug)))
post api("/import/bitbucket_server", user), params: {
bitbucket_server_url: base_uri,
bitbucket_server_username: user,
personal_access_token: token,
bitbucket_server_project: project_key,
bitbucket_server_repo: repo_slug,
new_namespace: 'new-namespace'
}
expect(response).to have_gitlab_http_status(:created)
expect(json_response).to be_a Hash
expect(json_response['full_path']).not_to eq("/#{user.namespace}/#{repo_slug}")
end
end
context 'with a private inaccessible namespace' do
let(:bitbucket_client) { instance_double(BitbucketServer::Client) }
let(:project) { create(:project, import_type: 'bitbucket', creator_id: user.id, import_source: 'asd/vim', namespace: 'private-group/vim') }
before do
Grape::Endpoint.before_each do |endpoint|
allow(endpoint).to receive(:client).and_return(bitbucket_client)
repo = double(name: repo_slug, full_path: "/private-group/#{repo_slug}")
allow(bitbucket_client).to receive(:repo).with(project_key, repo_slug).and_return(repo)
end
end
after do
Grape::Endpoint.before_each nil
end
it 'returns 401 response when user can not create projects in the chosen namespace' do
allow(Gitlab::BitbucketServerImport::ProjectCreator)
.to receive(:new).with(project_key, repo_slug, anything, repo_slug, an_instance_of(Group), user, anything)
.and_return(double(execute: build(:project)))
other_namespace = create(:group, :private, name: 'private-group')
post api("/import/bitbucket_server", user), params: {
bitbucket_server_url: base_uri,
bitbucket_server_username: user,
personal_access_token: token,
bitbucket_server_project: project_key,
bitbucket_server_repo: repo_slug,
new_namespace: other_namespace.name
}
expect(response).to have_gitlab_http_status(:unauthorized)
end
end
context 'with an inaccessible bitbucket server instance' do
let(:bitbucket_client) { instance_double(BitbucketServer::Client) }
let(:project) { create(:project, import_type: 'bitbucket', creator_id: user.id, import_source: 'asd/vim', namespace: 'private-group/vim') }
before do
Grape::Endpoint.before_each do |endpoint|
allow(endpoint).to receive(:client).and_return(bitbucket_client)
allow(bitbucket_client).to receive(:repo).with(project_key, repo_slug).and_raise(::BitbucketServer::Connection::ConnectionError)
end
end
it 'raises a connection error' do
post api("/import/bitbucket_server", user), params: {
bitbucket_server_url: base_uri,
bitbucket_server_username: user,
personal_access_token: token,
bitbucket_server_project: project_key,
bitbucket_server_repo: repo_slug,
new_namespace: 'new-namespace'
}
expect(response).to have_gitlab_http_status(:bad_request)
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Import::BitbucketServerService do
let_it_be(:user) { create(:user) }
let(:base_uri) { "https://test:7990" }
let(:token) { "asdasd12345" }
let(:secret) { "sekrettt" }
let(:project_key) { 'TES' }
let(:repo_slug) { 'vim' }
let(:repo) do
{
name: 'vim',
description: 'test',
visibility_level: Gitlab::VisibilityLevel::PUBLIC,
browse_url: 'http://repo.com/repo/repo',
clone_url: 'http://repo.com/repo/repo.git'
}
end
let(:client) { double(BitbucketServer::Client) }
let(:credentials) { { base_uri: base_uri, user: user, password: token } }
let(:params) { { bitbucket_server_url: base_uri, bitbucket_server_username: user, personal_access_token: token, bitbucket_server_project: project_key, bitbucket_server_repo: repo_slug } }
subject { described_class.new(client, user, params) }
before do
allow(subject).to receive(:authorized?).and_return(true)
end
context 'when no repo is found' do
before do
allow(subject).to receive(:authorized?).and_return(true)
allow(client).to receive(:repo).and_return(nil)
end
it 'returns an error' do
result = subject.execute(credentials)
expect(result).to include(
message: "Project #{project_key}/#{repo_slug} could not be found",
status: :error,
http_status: :unprocessable_entity
)
end
end
context 'when user is unauthorized' do
before do
allow(subject).to receive(:authorized?).and_return(false)
end
it 'returns an error' do
result = subject.execute(credentials)
expect(result).to include(
message: "You don't have permissions to create this project",
status: :error,
http_status: :unauthorized
)
end
end
context 'verify url' do
shared_examples 'denies local request' do
before do
allow(client).to receive(:repo).with(project_key, repo_slug).and_return(double(repo))
end
it 'does not allow requests' do
result = subject.execute(credentials)
expect(result[:status]).to eq(:error)
expect(result[:message]).to include("Invalid URL:")
end
end
context 'when host is localhost' do
before do
allow(subject).to receive(:url).and_return('https://localhost:3000')
end
include_examples 'denies local request'
end
context 'when host is on local network' do
before do
allow(subject).to receive(:url).and_return('https://192.168.0.191')
end
include_examples 'denies local request'
end
context 'when host is ftp protocol' do
before do
allow(subject).to receive(:url).and_return('ftp://testing')
end
include_examples 'denies local request'
end
end
it 'raises an exception for unknown error causes' do
exception = StandardError.new('Not Implemented')
allow(client).to receive(:repo).and_raise(exception)
expect(Gitlab::Import::Logger).not_to receive(:error)
expect { subject.execute(credentials) }.to raise_error(exception)
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment