2012-11-24 18:04:13 -05:00
|
|
|
require 'spec_helper'
|
|
|
|
|
2013-06-23 13:25:06 -04:00
|
|
|
describe Projects::MergeRequestsController do
|
2014-01-22 14:03:52 -05:00
|
|
|
let(:project) { create(:project) }
|
Use CTEs for nested groups and authorizations
This commit introduces the usage of Common Table Expressions (CTEs) to
efficiently retrieve nested group hierarchies, without having to rely on
the "routes" table (which is an _incredibly_ inefficient way of getting
the data). This requires a patch to ActiveRecord (found in the added
initializer) to work properly as ActiveRecord doesn't support WITH
statements properly out of the box.
Unfortunately MySQL provides no efficient way of getting nested groups.
For example, the old routes setup could easily take 5-10 seconds
depending on the amount of "routes" in a database. Providing vastly
different logic for both MySQL and PostgreSQL will negatively impact the
development process. Because of this the various nested groups related
methods return empty relations when used in combination with MySQL.
For project authorizations the logic is split up into two classes:
* Gitlab::ProjectAuthorizations::WithNestedGroups
* Gitlab::ProjectAuthorizations::WithoutNestedGroups
Both classes get the fresh project authorizations (= as they should be
in the "project_authorizations" table), including nested groups if
PostgreSQL is used. The logic of these two classes is quite different
apart from their public interface. This complicates development a bit,
but unfortunately there is no way around this.
This commit also introduces Gitlab::GroupHierarchy. This class can be
used to get the ancestors and descendants of a base relation, or both by
using a UNION. This in turn is used by methods such as:
* Namespace#ancestors
* Namespace#descendants
* User#all_expanded_groups
Again this class relies on CTEs and thus only works on PostgreSQL. The
Namespace methods will return an empty relation when MySQL is used,
while User#all_expanded_groups will return only the groups a user is a
direct member of.
Performance wise the impact is quite large. For example, on GitLab.com
Namespace#descendants used to take around 580 ms to retrieve data for a
particular user. Using CTEs we are able to reduce this down to roughly 1
millisecond, returning the exact same data.
== On The Fly Refreshing
Refreshing of authorizations on the fly (= when
users.authorized_projects_populated was not set) is removed with this
commit. This simplifies the code, and ensures any queries used for
authorizations are not mutated because they are executed in a Rails
scope (e.g. Project.visible_to_user).
This commit includes a migration to schedule refreshing authorizations
for all users, ensuring all of them have their authorizations in place.
Said migration schedules users in batches of 5000, with 5 minutes
between every batch to smear the load around a bit.
== Spec Changes
This commit also introduces some changes to various specs. For example,
some specs for ProjectTeam assumed that creating a personal project
would _not_ lead to the owner having access, which is incorrect. Because
we also no longer refresh authorizations on the fly for new users some
code had to be added to the "empty_project" factory. This chunk of code
ensures that the owner's permissions are refreshed after creating the
project, something that is normally done in Projects::CreateService.
2017-04-24 11:19:22 -04:00
|
|
|
let(:user) { project.owner }
|
2014-08-06 02:07:11 -04:00
|
|
|
let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
|
2016-07-27 12:54:04 -04:00
|
|
|
let(:merge_request_with_conflicts) do
|
2016-08-05 07:15:06 -04:00
|
|
|
create(:merge_request, source_branch: 'conflict-resolvable', target_branch: 'conflict-start', source_project: project) do |mr|
|
2016-07-27 12:54:04 -04:00
|
|
|
mr.mark_as_unmergeable
|
|
|
|
end
|
|
|
|
end
|
2012-11-24 18:04:13 -05:00
|
|
|
|
|
|
|
before do
|
|
|
|
sign_in(user)
|
|
|
|
end
|
|
|
|
|
2016-07-08 13:11:47 -04:00
|
|
|
describe 'GET new' do
|
2015-12-04 14:31:01 -05:00
|
|
|
context 'merge request that removes a submodule' do
|
|
|
|
render_views
|
|
|
|
|
|
|
|
let(:fork_project) { create(:forked_project_with_submodules) }
|
2017-02-01 14:06:11 -05:00
|
|
|
before { fork_project.team << [user, :master] }
|
2015-12-04 14:31:01 -05:00
|
|
|
|
2017-02-01 14:06:11 -05:00
|
|
|
context 'when rendering HTML response' do
|
|
|
|
it 'renders new merge request widget template' do
|
|
|
|
submit_new_merge_request
|
|
|
|
|
|
|
|
expect(response).to be_success
|
|
|
|
end
|
2015-12-04 14:31:01 -05:00
|
|
|
end
|
|
|
|
|
2017-02-01 14:06:11 -05:00
|
|
|
context 'when rendering JSON response' do
|
2017-02-07 11:48:42 -05:00
|
|
|
before do
|
|
|
|
create(:ci_pipeline, sha: fork_project.commit('remove-submodule').id,
|
|
|
|
ref: 'remove-submodule',
|
|
|
|
project: fork_project)
|
|
|
|
end
|
|
|
|
|
2017-02-01 14:06:11 -05:00
|
|
|
it 'renders JSON including serialized pipelines' do
|
|
|
|
submit_new_merge_request(format: :json)
|
2015-12-04 14:31:01 -05:00
|
|
|
|
2017-02-01 14:06:11 -05:00
|
|
|
expect(response).to be_ok
|
2017-02-16 07:52:07 -05:00
|
|
|
expect(json_response).to have_key 'pipelines'
|
|
|
|
expect(json_response['pipelines']).not_to be_empty
|
2017-02-01 14:06:11 -05:00
|
|
|
end
|
2015-12-04 14:31:01 -05:00
|
|
|
end
|
|
|
|
end
|
2017-02-01 14:06:11 -05:00
|
|
|
|
|
|
|
def submit_new_merge_request(format: :html)
|
|
|
|
get :new,
|
|
|
|
namespace_id: fork_project.namespace.to_param,
|
2017-02-23 18:55:01 -05:00
|
|
|
project_id: fork_project,
|
2017-02-01 14:06:11 -05:00
|
|
|
merge_request: {
|
|
|
|
source_branch: 'remove-submodule',
|
2017-02-22 12:44:44 -05:00
|
|
|
target_branch: 'master'
|
|
|
|
},
|
2017-02-01 14:06:11 -05:00
|
|
|
format: format
|
|
|
|
end
|
2015-12-04 14:31:01 -05:00
|
|
|
end
|
|
|
|
|
2017-05-09 00:15:34 -04:00
|
|
|
describe 'GET commit_change_content' do
|
|
|
|
it 'renders commit_change_content template' do
|
|
|
|
get :commit_change_content,
|
|
|
|
namespace_id: project.namespace.to_param,
|
|
|
|
project_id: project,
|
|
|
|
id: merge_request.iid,
|
|
|
|
format: 'html'
|
|
|
|
|
|
|
|
expect(response).to render_template('_commit_change_content')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-11-10 19:04:28 -05:00
|
|
|
shared_examples "loads labels" do |action|
|
|
|
|
it "loads labels into the @labels variable" do
|
|
|
|
get action,
|
|
|
|
namespace_id: project.namespace.to_param,
|
2017-02-23 18:55:01 -05:00
|
|
|
project_id: project,
|
2016-11-10 19:04:28 -05:00
|
|
|
id: merge_request.iid,
|
|
|
|
format: 'html'
|
|
|
|
expect(assigns(:labels)).not_to be_nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-07-08 13:11:47 -04:00
|
|
|
describe "GET show" do
|
2017-05-09 00:15:34 -04:00
|
|
|
def go(extra_params = {})
|
|
|
|
params = {
|
|
|
|
namespace_id: project.namespace.to_param,
|
|
|
|
project_id: project,
|
|
|
|
id: merge_request.iid
|
|
|
|
}
|
2012-11-24 18:04:13 -05:00
|
|
|
|
2017-05-09 00:15:34 -04:00
|
|
|
get :show, params.merge(extra_params)
|
|
|
|
end
|
2012-11-24 18:04:13 -05:00
|
|
|
|
2017-05-09 00:15:34 -04:00
|
|
|
it_behaves_like "loads labels", :show
|
2016-11-10 19:04:28 -05:00
|
|
|
|
2017-05-09 00:15:34 -04:00
|
|
|
describe 'as html' do
|
|
|
|
it "renders merge request page" do
|
|
|
|
go(format: :html)
|
2012-11-24 18:04:13 -05:00
|
|
|
|
2017-05-09 00:15:34 -04:00
|
|
|
expect(response).to be_success
|
2012-11-24 18:04:13 -05:00
|
|
|
end
|
2017-05-09 00:15:34 -04:00
|
|
|
end
|
2012-11-24 18:04:13 -05:00
|
|
|
|
2017-05-09 00:15:34 -04:00
|
|
|
describe 'as json' do
|
|
|
|
context 'with basic param' do
|
|
|
|
it 'renders basic MR entity as json' do
|
|
|
|
go(basic: true, format: :json)
|
2012-11-24 18:04:13 -05:00
|
|
|
|
2017-05-09 00:15:34 -04:00
|
|
|
expect(response).to match_response_schema('entities/merge_request_basic')
|
|
|
|
end
|
2012-11-24 18:04:13 -05:00
|
|
|
end
|
|
|
|
|
2017-05-09 00:15:34 -04:00
|
|
|
context 'without basic param' do
|
|
|
|
it 'renders the merge request in the json format' do
|
|
|
|
go(format: :json)
|
2012-11-24 18:04:13 -05:00
|
|
|
|
2017-05-09 00:15:34 -04:00
|
|
|
expect(response).to match_response_schema('entities/merge_request')
|
|
|
|
end
|
2012-11-24 18:04:13 -05:00
|
|
|
end
|
2017-05-18 16:14:48 -04:00
|
|
|
|
2017-06-09 13:12:51 -04:00
|
|
|
context 'number of queries', :request_store do
|
2017-05-18 16:14:48 -04:00
|
|
|
it 'verifies number of queries' do
|
2017-05-19 09:19:04 -04:00
|
|
|
# pre-create objects
|
|
|
|
merge_request
|
|
|
|
|
2017-05-18 16:14:48 -04:00
|
|
|
recorded = ActiveRecord::QueryRecorder.new { go(format: :json) }
|
|
|
|
|
2017-06-08 13:03:08 -04:00
|
|
|
expect(recorded.count).to be_within(5).of(30)
|
2017-05-18 16:14:48 -04:00
|
|
|
expect(recorded.cached_count).to eq(0)
|
|
|
|
end
|
|
|
|
end
|
2012-11-24 18:04:13 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
describe "as diff" do
|
2016-05-12 14:50:49 -04:00
|
|
|
it "triggers workhorse to serve the request" do
|
2017-05-09 00:15:34 -04:00
|
|
|
go(format: :diff)
|
2012-11-24 18:04:13 -05:00
|
|
|
|
2016-06-08 08:30:15 -04:00
|
|
|
expect(response.headers[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-diff:")
|
2012-11-24 18:04:13 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "as patch" do
|
2016-06-10 08:57:50 -04:00
|
|
|
it 'triggers workhorse to serve the request' do
|
2017-05-09 00:15:34 -04:00
|
|
|
go(format: :patch)
|
2012-11-24 18:04:13 -05:00
|
|
|
|
2016-07-03 17:01:13 -04:00
|
|
|
expect(response.headers[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-format-patch:")
|
2012-11-24 18:04:13 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2015-04-13 01:27:45 -04:00
|
|
|
|
2016-07-08 13:11:47 -04:00
|
|
|
describe 'GET index' do
|
2017-01-23 15:40:25 -05:00
|
|
|
let!(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
|
|
|
|
|
2016-12-20 13:52:09 -05:00
|
|
|
def get_merge_requests(page = nil)
|
2016-02-17 21:15:13 -05:00
|
|
|
get :index,
|
|
|
|
namespace_id: project.namespace.to_param,
|
2017-02-23 18:55:01 -05:00
|
|
|
project_id: project,
|
2016-12-20 13:52:09 -05:00
|
|
|
state: 'opened', page: page.to_param
|
|
|
|
end
|
|
|
|
|
2017-01-23 15:40:25 -05:00
|
|
|
it_behaves_like "issuables list meta-data", :merge_request
|
|
|
|
|
2016-12-20 13:52:09 -05:00
|
|
|
context 'when page param' do
|
2016-12-21 10:02:05 -05:00
|
|
|
let(:last_page) { project.merge_requests.page().total_pages }
|
|
|
|
let!(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
|
|
|
|
|
2016-12-20 13:52:09 -05:00
|
|
|
it 'redirects to last_page if page number is larger than number of pages' do
|
|
|
|
get_merge_requests(last_page + 1)
|
|
|
|
|
2016-12-21 10:02:05 -05:00
|
|
|
expect(response).to redirect_to(namespace_project_merge_requests_path(page: last_page, state: controller.params[:state], scope: controller.params[:scope]))
|
2016-12-20 13:52:09 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'redirects to specified page' do
|
|
|
|
get_merge_requests(last_page)
|
|
|
|
|
|
|
|
expect(assigns(:merge_requests).current_page).to eq(last_page)
|
|
|
|
expect(response).to have_http_status(200)
|
|
|
|
end
|
2017-04-05 18:52:19 -04:00
|
|
|
|
|
|
|
it 'does not redirect to external sites when provided a host field' do
|
|
|
|
external_host = "www.example.com"
|
|
|
|
get :index,
|
|
|
|
namespace_id: project.namespace.to_param,
|
|
|
|
project_id: project,
|
|
|
|
state: 'opened',
|
|
|
|
page: (last_page + 1).to_param,
|
|
|
|
host: external_host
|
|
|
|
|
|
|
|
expect(response).to redirect_to(namespace_project_merge_requests_path(page: last_page, state: controller.params[:state], scope: controller.params[:scope]))
|
|
|
|
end
|
2016-02-17 21:15:13 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
context 'when filtering by opened state' do
|
|
|
|
context 'with opened merge requests' do
|
2016-07-25 14:16:19 -04:00
|
|
|
it 'lists those merge requests' do
|
2016-02-17 21:15:13 -05:00
|
|
|
get_merge_requests
|
|
|
|
|
|
|
|
expect(assigns(:merge_requests)).to include(merge_request)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with reopened merge requests' do
|
|
|
|
before do
|
|
|
|
merge_request.close!
|
|
|
|
merge_request.reopen!
|
|
|
|
end
|
|
|
|
|
2016-07-25 14:16:19 -04:00
|
|
|
it 'lists those merge requests' do
|
2016-02-17 21:15:13 -05:00
|
|
|
get_merge_requests
|
|
|
|
|
|
|
|
expect(assigns(:merge_requests)).to include(merge_request)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-07-08 13:11:47 -04:00
|
|
|
describe 'PUT update' do
|
2017-03-18 00:23:15 -04:00
|
|
|
context 'changing the assignee' do
|
|
|
|
it 'limits the attributes exposed on the assignee' do
|
|
|
|
assignee = create(:user)
|
|
|
|
project.add_developer(assignee)
|
|
|
|
|
|
|
|
put :update,
|
|
|
|
namespace_id: project.namespace.to_param,
|
|
|
|
project_id: project,
|
|
|
|
id: merge_request.iid,
|
|
|
|
merge_request: { assignee_id: assignee.id },
|
|
|
|
format: :json
|
|
|
|
body = JSON.parse(response.body)
|
|
|
|
|
|
|
|
expect(body['assignee'].keys)
|
|
|
|
.to match_array(%w(name username avatar_url))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-04-11 15:21:32 -04:00
|
|
|
context 'there is no source project' do
|
2016-04-12 14:39:33 -04:00
|
|
|
let(:project) { create(:project) }
|
|
|
|
let(:fork_project) { create(:forked_project_with_submodules) }
|
|
|
|
let(:merge_request) { create(:merge_request, source_project: fork_project, source_branch: 'add-submodule-version-bump', target_branch: 'master', target_project: project) }
|
2016-04-11 15:21:32 -04:00
|
|
|
|
|
|
|
before do
|
|
|
|
fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id)
|
|
|
|
fork_project.save
|
|
|
|
merge_request.reload
|
2016-04-12 14:39:33 -04:00
|
|
|
fork_project.destroy
|
2016-04-11 15:21:32 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'closes MR without errors' do
|
|
|
|
post :update,
|
2017-02-23 18:55:01 -05:00
|
|
|
namespace_id: project.namespace,
|
|
|
|
project_id: project,
|
2016-04-11 15:21:32 -04:00
|
|
|
id: merge_request.iid,
|
|
|
|
merge_request: {
|
|
|
|
state_event: 'close'
|
|
|
|
}
|
|
|
|
|
|
|
|
expect(response).to redirect_to([merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request])
|
|
|
|
expect(merge_request.reload.closed?).to be_truthy
|
|
|
|
end
|
2016-07-26 07:57:43 -04:00
|
|
|
|
2016-08-10 09:36:30 -04:00
|
|
|
it 'allows editing of a closed merge request' do
|
2016-07-26 07:57:43 -04:00
|
|
|
merge_request.close!
|
|
|
|
|
|
|
|
put :update,
|
2017-02-23 18:55:01 -05:00
|
|
|
namespace_id: project.namespace,
|
|
|
|
project_id: project,
|
2016-07-26 07:57:43 -04:00
|
|
|
id: merge_request.iid,
|
|
|
|
merge_request: {
|
|
|
|
title: 'New title'
|
|
|
|
}
|
|
|
|
|
|
|
|
expect(response).to redirect_to([merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request])
|
|
|
|
expect(merge_request.reload.title).to eq 'New title'
|
|
|
|
end
|
|
|
|
|
2016-08-10 09:36:30 -04:00
|
|
|
it 'does not allow to update target branch closed merge request' do
|
2016-07-26 07:57:43 -04:00
|
|
|
merge_request.close!
|
|
|
|
|
|
|
|
put :update,
|
2017-02-23 18:55:01 -05:00
|
|
|
namespace_id: project.namespace,
|
|
|
|
project_id: project,
|
2016-07-26 07:57:43 -04:00
|
|
|
id: merge_request.iid,
|
|
|
|
merge_request: {
|
|
|
|
target_branch: 'new_branch'
|
|
|
|
}
|
|
|
|
|
|
|
|
expect { merge_request.reload.target_branch }.not_to change { merge_request.target_branch }
|
|
|
|
end
|
2017-01-16 13:43:03 -05:00
|
|
|
|
|
|
|
it_behaves_like 'update invalid issuable', MergeRequest
|
2016-04-11 15:21:32 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-07-08 13:11:47 -04:00
|
|
|
describe 'POST merge' do
|
2016-06-01 08:25:44 -04:00
|
|
|
let(:base_params) do
|
|
|
|
{
|
2017-02-23 18:55:01 -05:00
|
|
|
namespace_id: project.namespace,
|
|
|
|
project_id: project,
|
2016-06-01 08:25:44 -04:00
|
|
|
id: merge_request.iid,
|
2017-05-09 00:15:34 -04:00
|
|
|
format: 'json'
|
2016-06-01 08:25:44 -04:00
|
|
|
}
|
|
|
|
end
|
|
|
|
|
2017-05-09 00:15:34 -04:00
|
|
|
context 'when user cannot access' do
|
Use CTEs for nested groups and authorizations
This commit introduces the usage of Common Table Expressions (CTEs) to
efficiently retrieve nested group hierarchies, without having to rely on
the "routes" table (which is an _incredibly_ inefficient way of getting
the data). This requires a patch to ActiveRecord (found in the added
initializer) to work properly as ActiveRecord doesn't support WITH
statements properly out of the box.
Unfortunately MySQL provides no efficient way of getting nested groups.
For example, the old routes setup could easily take 5-10 seconds
depending on the amount of "routes" in a database. Providing vastly
different logic for both MySQL and PostgreSQL will negatively impact the
development process. Because of this the various nested groups related
methods return empty relations when used in combination with MySQL.
For project authorizations the logic is split up into two classes:
* Gitlab::ProjectAuthorizations::WithNestedGroups
* Gitlab::ProjectAuthorizations::WithoutNestedGroups
Both classes get the fresh project authorizations (= as they should be
in the "project_authorizations" table), including nested groups if
PostgreSQL is used. The logic of these two classes is quite different
apart from their public interface. This complicates development a bit,
but unfortunately there is no way around this.
This commit also introduces Gitlab::GroupHierarchy. This class can be
used to get the ancestors and descendants of a base relation, or both by
using a UNION. This in turn is used by methods such as:
* Namespace#ancestors
* Namespace#descendants
* User#all_expanded_groups
Again this class relies on CTEs and thus only works on PostgreSQL. The
Namespace methods will return an empty relation when MySQL is used,
while User#all_expanded_groups will return only the groups a user is a
direct member of.
Performance wise the impact is quite large. For example, on GitLab.com
Namespace#descendants used to take around 580 ms to retrieve data for a
particular user. Using CTEs we are able to reduce this down to roughly 1
millisecond, returning the exact same data.
== On The Fly Refreshing
Refreshing of authorizations on the fly (= when
users.authorized_projects_populated was not set) is removed with this
commit. This simplifies the code, and ensures any queries used for
authorizations are not mutated because they are executed in a Rails
scope (e.g. Project.visible_to_user).
This commit includes a migration to schedule refreshing authorizations
for all users, ensuring all of them have their authorizations in place.
Said migration schedules users in batches of 5000, with 5 minutes
between every batch to smear the load around a bit.
== Spec Changes
This commit also introduces some changes to various specs. For example,
some specs for ProjectTeam assumed that creating a personal project
would _not_ lead to the owner having access, which is incorrect. Because
we also no longer refresh authorizations on the fly for new users some
code had to be added to the "empty_project" factory. This chunk of code
ensures that the owner's permissions are refreshed after creating the
project, something that is normally done in Projects::CreateService.
2017-04-24 11:19:22 -04:00
|
|
|
let(:user) { create(:user) }
|
|
|
|
|
2016-06-01 08:25:44 -04:00
|
|
|
before do
|
2017-05-09 00:15:34 -04:00
|
|
|
project.add_reporter(user)
|
|
|
|
xhr :post, :merge, base_params
|
2016-06-01 08:25:44 -04:00
|
|
|
end
|
|
|
|
|
2017-05-09 00:15:34 -04:00
|
|
|
it 'returns 404' do
|
|
|
|
expect(response).to have_http_status(404)
|
2016-06-01 08:25:44 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the merge request is not mergeable' do
|
|
|
|
before do
|
|
|
|
merge_request.update_attributes(title: "WIP: #{merge_request.title}")
|
|
|
|
|
|
|
|
post :merge, base_params
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns :failed' do
|
2017-05-09 00:15:34 -04:00
|
|
|
expect(json_response).to eq('status' => 'failed')
|
2016-06-01 08:25:44 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the sha parameter does not match the source SHA' do
|
|
|
|
before { post :merge, base_params.merge(sha: 'foo') }
|
|
|
|
|
|
|
|
it 'returns :sha_mismatch' do
|
2017-05-09 00:15:34 -04:00
|
|
|
expect(json_response).to eq('status' => 'sha_mismatch')
|
2016-06-01 08:25:44 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the sha parameter matches the source SHA' do
|
|
|
|
def merge_with_sha
|
2016-06-20 12:48:04 -04:00
|
|
|
post :merge, base_params.merge(sha: merge_request.diff_head_sha)
|
2016-06-01 08:25:44 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns :success' do
|
|
|
|
merge_with_sha
|
|
|
|
|
2017-05-09 00:15:34 -04:00
|
|
|
expect(json_response).to eq('status' => 'success')
|
2016-06-01 08:25:44 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'starts the merge immediately' do
|
|
|
|
expect(MergeWorker).to receive(:perform_async).with(merge_request.id, anything, anything)
|
|
|
|
|
|
|
|
merge_with_sha
|
|
|
|
end
|
|
|
|
|
2017-02-17 08:56:13 -05:00
|
|
|
context 'when the pipeline succeeds is passed' do
|
|
|
|
def merge_when_pipeline_succeeds
|
|
|
|
post :merge, base_params.merge(sha: merge_request.diff_head_sha, merge_when_pipeline_succeeds: '1')
|
2016-06-01 08:25:44 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
before do
|
2017-05-19 16:51:07 -04:00
|
|
|
create(:ci_empty_pipeline, project: project, sha: merge_request.diff_head_sha, ref: merge_request.source_branch, head_pipeline_of: merge_request)
|
2016-06-01 08:25:44 -04:00
|
|
|
end
|
|
|
|
|
2017-02-17 08:56:13 -05:00
|
|
|
it 'returns :merge_when_pipeline_succeeds' do
|
|
|
|
merge_when_pipeline_succeeds
|
2016-06-01 08:25:44 -04:00
|
|
|
|
2017-05-09 00:15:34 -04:00
|
|
|
expect(json_response).to eq('status' => 'merge_when_pipeline_succeeds')
|
2016-06-01 08:25:44 -04:00
|
|
|
end
|
|
|
|
|
2017-02-17 08:56:13 -05:00
|
|
|
it 'sets the MR to merge when the pipeline succeeds' do
|
|
|
|
service = double(:merge_when_pipeline_succeeds_service)
|
2016-06-01 08:25:44 -04:00
|
|
|
|
2017-02-22 17:54:59 -05:00
|
|
|
expect(MergeRequests::MergeWhenPipelineSucceedsService)
|
|
|
|
.to receive(:new).with(project, anything, anything)
|
|
|
|
.and_return(service)
|
2016-06-01 08:25:44 -04:00
|
|
|
expect(service).to receive(:execute).with(merge_request)
|
|
|
|
|
2017-02-17 08:56:13 -05:00
|
|
|
merge_when_pipeline_succeeds
|
2016-06-01 08:25:44 -04:00
|
|
|
end
|
2016-06-24 12:13:56 -04:00
|
|
|
|
2017-02-17 08:56:13 -05:00
|
|
|
context 'when project.only_allow_merge_if_pipeline_succeeds? is true' do
|
2016-06-24 12:13:56 -04:00
|
|
|
before do
|
2017-02-17 08:56:13 -05:00
|
|
|
project.update_column(:only_allow_merge_if_pipeline_succeeds, true)
|
2016-06-24 12:13:56 -04:00
|
|
|
end
|
|
|
|
|
2017-02-17 08:56:13 -05:00
|
|
|
it 'returns :merge_when_pipeline_succeeds' do
|
|
|
|
merge_when_pipeline_succeeds
|
2016-06-24 12:13:56 -04:00
|
|
|
|
2017-05-09 00:15:34 -04:00
|
|
|
expect(json_response).to eq('status' => 'merge_when_pipeline_succeeds')
|
2016-06-24 12:13:56 -04:00
|
|
|
end
|
|
|
|
end
|
2016-06-01 08:25:44 -04:00
|
|
|
end
|
2016-09-16 07:02:42 -04:00
|
|
|
|
2016-10-26 13:19:17 -04:00
|
|
|
describe 'only_allow_merge_if_all_discussions_are_resolved? setting' do
|
|
|
|
let(:merge_request) { create(:merge_request_with_diff_notes, source_project: project, author: user) }
|
|
|
|
|
|
|
|
context 'when enabled' do
|
|
|
|
before do
|
|
|
|
project.update_column(:only_allow_merge_if_all_discussions_are_resolved, true)
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with unresolved discussion' do
|
|
|
|
before do
|
|
|
|
expect(merge_request).not_to be_discussions_resolved
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns :failed' do
|
|
|
|
merge_with_sha
|
|
|
|
|
2017-05-09 00:15:34 -04:00
|
|
|
expect(json_response).to eq('status' => 'failed')
|
2016-10-26 13:19:17 -04:00
|
|
|
end
|
|
|
|
end
|
2016-09-16 07:02:42 -04:00
|
|
|
|
2016-10-26 13:19:17 -04:00
|
|
|
context 'with all discussions resolved' do
|
|
|
|
before do
|
|
|
|
merge_request.discussions.each { |d| d.resolve!(user) }
|
|
|
|
expect(merge_request).to be_discussions_resolved
|
|
|
|
end
|
2016-09-16 07:02:42 -04:00
|
|
|
|
2016-10-26 13:19:17 -04:00
|
|
|
it 'returns :success' do
|
|
|
|
merge_with_sha
|
2016-09-16 07:02:42 -04:00
|
|
|
|
2017-05-09 00:15:34 -04:00
|
|
|
expect(json_response).to eq('status' => 'success')
|
2016-10-26 13:19:17 -04:00
|
|
|
end
|
2016-09-16 07:02:42 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-10-26 13:19:17 -04:00
|
|
|
context 'when disabled' do
|
|
|
|
before do
|
|
|
|
project.update_column(:only_allow_merge_if_all_discussions_are_resolved, false)
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with unresolved discussion' do
|
|
|
|
before do
|
|
|
|
expect(merge_request).not_to be_discussions_resolved
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns :success' do
|
|
|
|
merge_with_sha
|
|
|
|
|
2017-05-09 00:15:34 -04:00
|
|
|
expect(json_response).to eq('status' => 'success')
|
2016-10-26 13:19:17 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with all discussions resolved' do
|
|
|
|
before do
|
|
|
|
merge_request.discussions.each { |d| d.resolve!(user) }
|
|
|
|
expect(merge_request).to be_discussions_resolved
|
|
|
|
end
|
2016-09-16 07:02:42 -04:00
|
|
|
|
2016-10-26 13:19:17 -04:00
|
|
|
it 'returns :success' do
|
|
|
|
merge_with_sha
|
2016-09-16 07:02:42 -04:00
|
|
|
|
2017-05-09 00:15:34 -04:00
|
|
|
expect(json_response).to eq('status' => 'success')
|
2016-10-26 13:19:17 -04:00
|
|
|
end
|
2016-09-16 07:02:42 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2016-06-01 08:25:44 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-07-08 13:11:47 -04:00
|
|
|
describe "DELETE destroy" do
|
Use CTEs for nested groups and authorizations
This commit introduces the usage of Common Table Expressions (CTEs) to
efficiently retrieve nested group hierarchies, without having to rely on
the "routes" table (which is an _incredibly_ inefficient way of getting
the data). This requires a patch to ActiveRecord (found in the added
initializer) to work properly as ActiveRecord doesn't support WITH
statements properly out of the box.
Unfortunately MySQL provides no efficient way of getting nested groups.
For example, the old routes setup could easily take 5-10 seconds
depending on the amount of "routes" in a database. Providing vastly
different logic for both MySQL and PostgreSQL will negatively impact the
development process. Because of this the various nested groups related
methods return empty relations when used in combination with MySQL.
For project authorizations the logic is split up into two classes:
* Gitlab::ProjectAuthorizations::WithNestedGroups
* Gitlab::ProjectAuthorizations::WithoutNestedGroups
Both classes get the fresh project authorizations (= as they should be
in the "project_authorizations" table), including nested groups if
PostgreSQL is used. The logic of these two classes is quite different
apart from their public interface. This complicates development a bit,
but unfortunately there is no way around this.
This commit also introduces Gitlab::GroupHierarchy. This class can be
used to get the ancestors and descendants of a base relation, or both by
using a UNION. This in turn is used by methods such as:
* Namespace#ancestors
* Namespace#descendants
* User#all_expanded_groups
Again this class relies on CTEs and thus only works on PostgreSQL. The
Namespace methods will return an empty relation when MySQL is used,
while User#all_expanded_groups will return only the groups a user is a
direct member of.
Performance wise the impact is quite large. For example, on GitLab.com
Namespace#descendants used to take around 580 ms to retrieve data for a
particular user. Using CTEs we are able to reduce this down to roughly 1
millisecond, returning the exact same data.
== On The Fly Refreshing
Refreshing of authorizations on the fly (= when
users.authorized_projects_populated was not set) is removed with this
commit. This simplifies the code, and ensures any queries used for
authorizations are not mutated because they are executed in a Rails
scope (e.g. Project.visible_to_user).
This commit includes a migration to schedule refreshing authorizations
for all users, ensuring all of them have their authorizations in place.
Said migration schedules users in batches of 5000, with 5 minutes
between every batch to smear the load around a bit.
== Spec Changes
This commit also introduces some changes to various specs. For example,
some specs for ProjectTeam assumed that creating a personal project
would _not_ lead to the owner having access, which is incorrect. Because
we also no longer refresh authorizations on the fly for new users some
code had to be added to the "empty_project" factory. This chunk of code
ensures that the owner's permissions are refreshed after creating the
project, something that is normally done in Projects::CreateService.
2017-04-24 11:19:22 -04:00
|
|
|
let(:user) { create(:user) }
|
|
|
|
|
2016-03-21 09:12:52 -04:00
|
|
|
it "denies access to users unless they're admin or project owner" do
|
2017-02-23 18:55:01 -05:00
|
|
|
delete :destroy, namespace_id: project.namespace, project_id: project, id: merge_request.iid
|
2016-02-26 03:55:43 -05:00
|
|
|
|
2016-06-27 14:10:42 -04:00
|
|
|
expect(response).to have_http_status(404)
|
2016-02-26 03:55:43 -05:00
|
|
|
end
|
|
|
|
|
2016-03-21 09:12:52 -04:00
|
|
|
context "when the user is owner" do
|
|
|
|
let(:owner) { create(:user) }
|
|
|
|
let(:namespace) { create(:namespace, owner: owner) }
|
|
|
|
let(:project) { create(:project, namespace: namespace) }
|
|
|
|
|
|
|
|
before { sign_in owner }
|
2016-02-26 03:55:43 -05:00
|
|
|
|
2016-03-21 09:12:52 -04:00
|
|
|
it "deletes the merge request" do
|
2017-02-23 18:55:01 -05:00
|
|
|
delete :destroy, namespace_id: project.namespace, project_id: project, id: merge_request.iid
|
2016-02-26 03:55:43 -05:00
|
|
|
|
2016-06-27 14:10:42 -04:00
|
|
|
expect(response).to have_http_status(302)
|
2016-03-21 09:12:52 -04:00
|
|
|
expect(controller).to set_flash[:notice].to(/The merge request was successfully deleted\./).now
|
2016-02-26 03:55:43 -05:00
|
|
|
end
|
2016-09-01 18:12:05 -04:00
|
|
|
|
|
|
|
it 'delegates the update of the todos count cache to TodoService' do
|
|
|
|
expect_any_instance_of(TodoService).to receive(:destroy_merge_request).with(merge_request, owner).once
|
|
|
|
|
2017-02-23 18:55:01 -05:00
|
|
|
delete :destroy, namespace_id: project.namespace, project_id: project, id: merge_request.iid
|
2016-09-01 18:12:05 -04:00
|
|
|
end
|
2016-02-26 03:55:43 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-07-08 13:11:47 -04:00
|
|
|
describe 'GET diffs' do
|
2016-06-28 12:25:32 -04:00
|
|
|
def go(extra_params = {})
|
|
|
|
params = {
|
|
|
|
namespace_id: project.namespace.to_param,
|
2017-02-23 18:55:01 -05:00
|
|
|
project_id: project,
|
2016-06-28 12:25:32 -04:00
|
|
|
id: merge_request.iid
|
|
|
|
}
|
|
|
|
|
|
|
|
get :diffs, params.merge(extra_params)
|
2015-04-13 01:27:45 -04:00
|
|
|
end
|
|
|
|
|
2016-11-10 19:04:28 -05:00
|
|
|
it_behaves_like "loads labels", :diffs
|
|
|
|
|
2016-06-28 12:25:32 -04:00
|
|
|
context 'with default params' do
|
|
|
|
context 'as html' do
|
|
|
|
before { go(format: 'html') }
|
2015-06-17 16:12:28 -04:00
|
|
|
|
2016-06-28 12:25:32 -04:00
|
|
|
it 'renders the diff template' do
|
|
|
|
expect(response).to render_template('diffs')
|
|
|
|
end
|
2015-06-17 16:12:28 -04:00
|
|
|
end
|
|
|
|
|
2016-06-28 12:25:32 -04:00
|
|
|
context 'as json' do
|
|
|
|
before { go(format: 'json') }
|
2015-06-17 16:12:28 -04:00
|
|
|
|
2016-06-28 12:25:32 -04:00
|
|
|
it 'renders the diffs template to a string' do
|
|
|
|
expect(response).to render_template('projects/merge_requests/show/_diffs')
|
2017-01-30 09:31:28 -05:00
|
|
|
expect(json_response).to have_key('html')
|
2016-06-28 12:25:32 -04:00
|
|
|
end
|
2015-06-17 16:12:28 -04:00
|
|
|
end
|
|
|
|
|
2016-06-28 12:25:32 -04:00
|
|
|
context 'with forked projects with submodules' do
|
|
|
|
render_views
|
2015-06-17 16:12:28 -04:00
|
|
|
|
2016-06-28 12:25:32 -04:00
|
|
|
let(:project) { create(:project) }
|
|
|
|
let(:fork_project) { create(:forked_project_with_submodules) }
|
|
|
|
let(:merge_request) { create(:merge_request_with_diffs, source_project: fork_project, source_branch: 'add-submodule-version-bump', target_branch: 'master', target_project: project) }
|
2015-06-17 16:12:28 -04:00
|
|
|
|
2016-06-28 12:25:32 -04:00
|
|
|
before do
|
|
|
|
fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id)
|
|
|
|
fork_project.save
|
|
|
|
merge_request.reload
|
|
|
|
go(format: 'json')
|
|
|
|
end
|
2015-06-17 16:12:28 -04:00
|
|
|
|
2016-06-28 12:25:32 -04:00
|
|
|
it 'renders' do
|
|
|
|
expect(response).to be_success
|
|
|
|
expect(response.body).to have_content('Subproject commit')
|
|
|
|
end
|
2015-06-17 16:12:28 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-06-28 12:25:32 -04:00
|
|
|
context 'with ignore_whitespace_change' do
|
|
|
|
context 'as html' do
|
|
|
|
before { go(format: 'html', w: 1) }
|
2015-10-21 20:55:35 -04:00
|
|
|
|
2016-06-28 12:25:32 -04:00
|
|
|
it 'renders the diff template' do
|
|
|
|
expect(response).to render_template('diffs')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'as json' do
|
|
|
|
before { go(format: 'json', w: 1) }
|
2015-10-21 20:55:35 -04:00
|
|
|
|
2016-06-28 12:25:32 -04:00
|
|
|
it 'renders the diffs template to a string' do
|
|
|
|
expect(response).to render_template('projects/merge_requests/show/_diffs')
|
2017-01-30 09:31:28 -05:00
|
|
|
expect(json_response).to have_key('html')
|
2016-06-28 12:25:32 -04:00
|
|
|
end
|
2015-10-21 20:55:35 -04:00
|
|
|
end
|
|
|
|
end
|
2016-02-05 06:34:51 -05:00
|
|
|
|
2016-06-28 12:25:32 -04:00
|
|
|
context 'with view' do
|
|
|
|
before { go(view: 'parallel') }
|
2015-10-21 20:55:35 -04:00
|
|
|
|
2016-06-28 12:25:32 -04:00
|
|
|
it 'saves the preferred diff view in a cookie' do
|
|
|
|
expect(response.cookies['diff_view']).to eq('parallel')
|
2015-10-21 20:55:35 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-07-08 13:11:47 -04:00
|
|
|
describe 'GET diff_for_path' do
|
2016-06-28 12:25:32 -04:00
|
|
|
def diff_for_path(extra_params = {})
|
2016-02-05 16:03:20 -05:00
|
|
|
params = {
|
|
|
|
namespace_id: project.namespace.to_param,
|
2017-02-23 18:55:01 -05:00
|
|
|
project_id: project
|
2016-02-05 16:03:20 -05:00
|
|
|
}
|
|
|
|
|
2016-06-28 12:25:32 -04:00
|
|
|
get :diff_for_path, params.merge(extra_params)
|
2016-02-05 16:03:20 -05:00
|
|
|
end
|
|
|
|
|
2016-06-28 12:25:32 -04:00
|
|
|
context 'when an ID param is passed' do
|
|
|
|
let(:existing_path) { 'files/ruby/popen.rb' }
|
2016-02-05 06:34:51 -05:00
|
|
|
|
2016-06-28 12:25:32 -04:00
|
|
|
context 'when the merge request exists' do
|
|
|
|
context 'when the user can view the merge request' do
|
|
|
|
context 'when the path exists in the diff' do
|
|
|
|
it 'enables diff notes' do
|
2016-07-08 17:50:06 -04:00
|
|
|
diff_for_path(id: merge_request.iid, old_path: existing_path, new_path: existing_path)
|
2016-06-28 12:25:32 -04:00
|
|
|
|
|
|
|
expect(assigns(:diff_notes_disabled)).to be_falsey
|
2017-03-09 20:29:11 -05:00
|
|
|
expect(assigns(:new_diff_note_attrs)).to eq(noteable_type: 'MergeRequest',
|
2017-03-13 14:05:02 -04:00
|
|
|
noteable_id: merge_request.id)
|
2016-06-28 12:25:32 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'only renders the diffs for the path given' do
|
2016-07-26 03:21:42 -04:00
|
|
|
expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs|
|
|
|
|
expect(diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path)
|
|
|
|
meth.call(diffs)
|
2016-06-28 12:25:32 -04:00
|
|
|
end
|
|
|
|
|
2016-07-08 17:50:06 -04:00
|
|
|
diff_for_path(id: merge_request.iid, old_path: existing_path, new_path: existing_path)
|
2016-06-28 12:25:32 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the path does not exist in the diff' do
|
2016-07-08 17:50:06 -04:00
|
|
|
before { diff_for_path(id: merge_request.iid, old_path: 'files/ruby/nopen.rb', new_path: 'files/ruby/nopen.rb') }
|
2016-06-28 12:25:32 -04:00
|
|
|
|
|
|
|
it 'returns a 404' do
|
|
|
|
expect(response).to have_http_status(404)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the user cannot view the merge request' do
|
|
|
|
before do
|
|
|
|
project.team.truncate
|
2016-07-08 17:50:06 -04:00
|
|
|
diff_for_path(id: merge_request.iid, old_path: existing_path, new_path: existing_path)
|
2016-06-28 12:25:32 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns a 404' do
|
|
|
|
expect(response).to have_http_status(404)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the merge request does not exist' do
|
2016-07-08 17:50:06 -04:00
|
|
|
before { diff_for_path(id: merge_request.iid.succ, old_path: existing_path, new_path: existing_path) }
|
2016-06-28 12:25:32 -04:00
|
|
|
|
|
|
|
it 'returns a 404' do
|
|
|
|
expect(response).to have_http_status(404)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the merge request belongs to a different project' do
|
|
|
|
let(:other_project) { create(:empty_project) }
|
|
|
|
|
|
|
|
before do
|
|
|
|
other_project.team << [user, :master]
|
2017-02-23 18:55:01 -05:00
|
|
|
diff_for_path(id: merge_request.iid, old_path: existing_path, new_path: existing_path, project_id: other_project)
|
2016-06-28 12:25:32 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns a 404' do
|
|
|
|
expect(response).to have_http_status(404)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when source and target params are passed' do
|
|
|
|
let(:existing_path) { 'files/ruby/feature.rb' }
|
|
|
|
|
|
|
|
context 'when both branches are in the same project' do
|
|
|
|
it 'disables diff notes' do
|
2016-07-08 17:50:06 -04:00
|
|
|
diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_branch: 'feature', target_branch: 'master' })
|
2016-06-28 12:25:32 -04:00
|
|
|
|
|
|
|
expect(assigns(:diff_notes_disabled)).to be_truthy
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'only renders the diffs for the path given' do
|
2016-07-26 03:21:42 -04:00
|
|
|
expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs|
|
|
|
|
expect(diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path)
|
|
|
|
meth.call(diffs)
|
2016-06-28 12:25:32 -04:00
|
|
|
end
|
|
|
|
|
2016-07-08 17:50:06 -04:00
|
|
|
diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_branch: 'feature', target_branch: 'master' })
|
2016-06-28 12:25:32 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the source branch is in a different project to the target' do
|
|
|
|
let(:other_project) { create(:project) }
|
|
|
|
|
|
|
|
before { other_project.team << [user, :master] }
|
|
|
|
|
|
|
|
context 'when the path exists in the diff' do
|
|
|
|
it 'disables diff notes' do
|
2016-07-08 17:50:06 -04:00
|
|
|
diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' })
|
2016-06-28 12:25:32 -04:00
|
|
|
|
|
|
|
expect(assigns(:diff_notes_disabled)).to be_truthy
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'only renders the diffs for the path given' do
|
2016-07-26 03:21:42 -04:00
|
|
|
expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs|
|
|
|
|
expect(diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path)
|
|
|
|
meth.call(diffs)
|
2016-06-28 12:25:32 -04:00
|
|
|
end
|
|
|
|
|
2016-07-08 17:50:06 -04:00
|
|
|
diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' })
|
2016-06-28 12:25:32 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the path does not exist in the diff' do
|
2016-07-08 17:50:06 -04:00
|
|
|
before { diff_for_path(old_path: 'files/ruby/nopen.rb', new_path: 'files/ruby/nopen.rb', merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' }) }
|
2016-06-28 12:25:32 -04:00
|
|
|
|
|
|
|
it 'returns a 404' do
|
|
|
|
expect(response).to have_http_status(404)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2016-02-05 06:34:51 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-07-08 17:50:06 -04:00
|
|
|
describe 'GET commits' do
|
2015-06-17 16:12:28 -04:00
|
|
|
def go(format: 'html')
|
2015-06-23 01:24:39 -04:00
|
|
|
get :commits,
|
|
|
|
namespace_id: project.namespace.to_param,
|
2017-02-23 18:55:01 -05:00
|
|
|
project_id: project,
|
2015-06-23 01:24:39 -04:00
|
|
|
id: merge_request.iid,
|
|
|
|
format: format
|
2015-06-17 16:12:28 -04:00
|
|
|
end
|
|
|
|
|
2016-11-10 19:04:28 -05:00
|
|
|
it_behaves_like "loads labels", :commits
|
|
|
|
|
2015-06-17 16:12:28 -04:00
|
|
|
context 'as html' do
|
|
|
|
it 'renders the show template' do
|
|
|
|
go
|
|
|
|
|
|
|
|
expect(response).to render_template('show')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'as json' do
|
|
|
|
it 'renders the commits template to a string' do
|
|
|
|
go format: 'json'
|
|
|
|
|
|
|
|
expect(response).to render_template('projects/merge_requests/show/_commits')
|
2017-01-30 09:31:28 -05:00
|
|
|
expect(json_response).to have_key('html')
|
2015-06-17 16:12:28 -04:00
|
|
|
end
|
2015-04-13 01:27:45 -04:00
|
|
|
end
|
|
|
|
end
|
2016-07-27 07:42:18 -04:00
|
|
|
|
2016-11-10 19:04:28 -05:00
|
|
|
describe 'GET pipelines' do
|
2017-01-27 08:45:56 -05:00
|
|
|
before do
|
|
|
|
create(:ci_pipeline, project: merge_request.source_project,
|
|
|
|
ref: merge_request.source_branch,
|
|
|
|
sha: merge_request.diff_head_sha)
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when using HTML format' do
|
|
|
|
it_behaves_like "loads labels", :pipelines
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when using JSON format' do
|
|
|
|
before do
|
|
|
|
get :pipelines,
|
|
|
|
namespace_id: project.namespace.to_param,
|
2017-02-23 18:55:01 -05:00
|
|
|
project_id: project,
|
2017-01-27 08:45:56 -05:00
|
|
|
id: merge_request.iid,
|
|
|
|
format: :json
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'responds with serialized pipelines' do
|
2017-02-02 14:43:22 -05:00
|
|
|
expect(json_response).not_to be_empty
|
2017-01-27 08:45:56 -05:00
|
|
|
end
|
|
|
|
end
|
2016-11-10 19:04:28 -05:00
|
|
|
end
|
|
|
|
|
2016-07-27 07:42:18 -04:00
|
|
|
describe 'GET conflicts' do
|
2016-07-29 09:51:11 -04:00
|
|
|
context 'when the conflicts cannot be resolved in the UI' do
|
2016-07-27 07:42:18 -04:00
|
|
|
before do
|
2017-02-22 17:50:15 -05:00
|
|
|
allow_any_instance_of(Gitlab::Conflict::Parser).
|
|
|
|
to receive(:parse).and_raise(Gitlab::Conflict::Parser::UnmergeableFile)
|
2016-07-29 09:51:11 -04:00
|
|
|
|
2016-07-27 07:42:18 -04:00
|
|
|
get :conflicts,
|
|
|
|
namespace_id: merge_request_with_conflicts.project.namespace.to_param,
|
2017-02-23 18:55:01 -05:00
|
|
|
project_id: merge_request_with_conflicts.project,
|
2016-07-27 07:42:18 -04:00
|
|
|
id: merge_request_with_conflicts.iid,
|
|
|
|
format: 'json'
|
|
|
|
end
|
|
|
|
|
2016-07-29 09:51:11 -04:00
|
|
|
it 'returns a 200 status code' do
|
|
|
|
expect(response).to have_http_status(:ok)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns JSON with a message' do
|
2016-08-02 08:56:50 -04:00
|
|
|
expect(json_response.keys).to contain_exactly('message', 'type')
|
2016-07-29 09:51:11 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with valid conflicts' do
|
|
|
|
before do
|
|
|
|
get :conflicts,
|
|
|
|
namespace_id: merge_request_with_conflicts.project.namespace.to_param,
|
2017-02-23 18:55:01 -05:00
|
|
|
project_id: merge_request_with_conflicts.project,
|
2016-07-29 09:51:11 -04:00
|
|
|
id: merge_request_with_conflicts.iid,
|
|
|
|
format: 'json'
|
|
|
|
end
|
2016-07-27 07:42:18 -04:00
|
|
|
|
2016-09-02 11:16:54 -04:00
|
|
|
it 'matches the schema' do
|
|
|
|
expect(response).to match_response_schema('conflicts')
|
|
|
|
end
|
|
|
|
|
2016-07-27 07:42:18 -04:00
|
|
|
it 'includes meta info about the MR' do
|
|
|
|
expect(json_response['commit_message']).to include('Merge branch')
|
|
|
|
expect(json_response['commit_sha']).to match(/\h{40}/)
|
|
|
|
expect(json_response['source_branch']).to eq(merge_request_with_conflicts.source_branch)
|
|
|
|
expect(json_response['target_branch']).to eq(merge_request_with_conflicts.target_branch)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'includes each file that has conflicts' do
|
|
|
|
filenames = json_response['files'].map { |file| file['new_path'] }
|
|
|
|
|
|
|
|
expect(filenames).to contain_exactly('files/ruby/popen.rb', 'files/ruby/regex.rb')
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'splits files into sections with lines' do
|
|
|
|
json_response['files'].each do |file|
|
|
|
|
file['sections'].each do |section|
|
|
|
|
expect(section).to include('conflict', 'lines')
|
|
|
|
|
|
|
|
section['lines'].each do |line|
|
|
|
|
if section['conflict']
|
2017-02-22 12:46:57 -05:00
|
|
|
expect(line['type']).to be_in(%w(old new))
|
2016-07-27 07:42:18 -04:00
|
|
|
expect(line.values_at('old_line', 'new_line')).to contain_exactly(nil, a_kind_of(Integer))
|
|
|
|
else
|
|
|
|
if line['type'].nil?
|
|
|
|
expect(line['old_line']).not_to eq(nil)
|
|
|
|
expect(line['new_line']).not_to eq(nil)
|
|
|
|
else
|
|
|
|
expect(line['type']).to eq('match')
|
|
|
|
expect(line['old_line']).to eq(nil)
|
|
|
|
expect(line['new_line']).to eq(nil)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'has unique section IDs across files' do
|
|
|
|
section_ids = json_response['files'].flat_map do |file|
|
|
|
|
file['sections'].map { |section| section['id'] }.compact
|
|
|
|
end
|
|
|
|
|
|
|
|
expect(section_ids.uniq).to eq(section_ids)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2016-07-27 12:54:04 -04:00
|
|
|
|
2017-05-09 00:15:34 -04:00
|
|
|
describe 'POST remove_wip' do
|
|
|
|
before do
|
2016-09-08 05:18:41 -04:00
|
|
|
merge_request.title = merge_request.wip_title
|
|
|
|
merge_request.save
|
|
|
|
|
2017-05-09 00:15:34 -04:00
|
|
|
xhr :post, :remove_wip,
|
|
|
|
namespace_id: merge_request.project.namespace.to_param,
|
|
|
|
project_id: merge_request.project,
|
|
|
|
id: merge_request.iid,
|
|
|
|
format: :json
|
|
|
|
end
|
2016-09-08 05:18:41 -04:00
|
|
|
|
2017-05-09 00:15:34 -04:00
|
|
|
it 'removes the wip status' do
|
2016-09-08 05:18:41 -04:00
|
|
|
expect(merge_request.reload.title).to eq(merge_request.wipless_title)
|
2016-10-05 08:57:57 -04:00
|
|
|
end
|
2017-05-09 00:15:34 -04:00
|
|
|
|
|
|
|
it 'renders MergeRequest as JSON' do
|
|
|
|
expect(json_response.keys).to include('id', 'iid', 'description')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe 'POST cancel_merge_when_pipeline_succeeds' do
|
|
|
|
subject do
|
|
|
|
xhr :post, :cancel_merge_when_pipeline_succeeds,
|
|
|
|
namespace_id: merge_request.project.namespace.to_param,
|
|
|
|
project_id: merge_request.project,
|
|
|
|
id: merge_request.iid,
|
|
|
|
format: :json
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'calls MergeRequests::MergeWhenPipelineSucceedsService' do
|
|
|
|
mwps_service = double
|
|
|
|
|
|
|
|
allow(MergeRequests::MergeWhenPipelineSucceedsService)
|
|
|
|
.to receive(:new)
|
|
|
|
.and_return(mwps_service)
|
|
|
|
|
|
|
|
expect(mwps_service).to receive(:cancel).with(merge_request)
|
|
|
|
|
|
|
|
subject
|
|
|
|
end
|
|
|
|
|
|
|
|
it { is_expected.to have_http_status(:success) }
|
|
|
|
|
|
|
|
it 'renders MergeRequest as JSON' do
|
|
|
|
subject
|
|
|
|
|
|
|
|
expect(json_response.keys).to include('id', 'iid', 'description')
|
|
|
|
end
|
2016-09-01 08:59:10 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
describe 'GET conflict_for_path' do
|
|
|
|
def conflict_for_path(path)
|
|
|
|
get :conflict_for_path,
|
|
|
|
namespace_id: merge_request_with_conflicts.project.namespace.to_param,
|
2017-02-23 18:55:01 -05:00
|
|
|
project_id: merge_request_with_conflicts.project,
|
2016-09-01 08:59:10 -04:00
|
|
|
id: merge_request_with_conflicts.iid,
|
|
|
|
old_path: path,
|
|
|
|
new_path: path,
|
|
|
|
format: 'json'
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the conflicts cannot be resolved in the UI' do
|
|
|
|
before do
|
2017-02-22 17:50:15 -05:00
|
|
|
allow_any_instance_of(Gitlab::Conflict::Parser).
|
|
|
|
to receive(:parse).and_raise(Gitlab::Conflict::Parser::UnmergeableFile)
|
2016-09-01 08:59:10 -04:00
|
|
|
|
|
|
|
conflict_for_path('files/ruby/regex.rb')
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns a 404 status code' do
|
|
|
|
expect(response).to have_http_status(:not_found)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the file does not exist cannot be resolved in the UI' do
|
|
|
|
before { conflict_for_path('files/ruby/regexp.rb') }
|
|
|
|
|
|
|
|
it 'returns a 404 status code' do
|
|
|
|
expect(response).to have_http_status(:not_found)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with an existing file' do
|
|
|
|
let(:path) { 'files/ruby/regex.rb' }
|
|
|
|
|
|
|
|
before { conflict_for_path(path) }
|
|
|
|
|
|
|
|
it 'returns a 200 status code' do
|
|
|
|
expect(response).to have_http_status(:ok)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns the file in JSON format' do
|
2017-05-11 11:23:02 -04:00
|
|
|
content = MergeRequests::Conflicts::ListService.new(merge_request_with_conflicts).
|
|
|
|
file_for_path(path, path).
|
|
|
|
content
|
2016-09-01 08:59:10 -04:00
|
|
|
|
|
|
|
expect(json_response).to include('old_path' => path,
|
|
|
|
'new_path' => path,
|
|
|
|
'blob_icon' => 'file-text-o',
|
|
|
|
'blob_path' => a_string_ending_with(path),
|
2016-10-13 05:34:15 -04:00
|
|
|
'blob_ace_mode' => 'ruby',
|
2016-09-01 08:59:10 -04:00
|
|
|
'content' => content)
|
|
|
|
end
|
2016-09-08 05:18:41 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-07-27 12:54:04 -04:00
|
|
|
context 'POST resolve_conflicts' do
|
2016-07-29 09:51:11 -04:00
|
|
|
let!(:original_head_sha) { merge_request_with_conflicts.diff_head_sha }
|
|
|
|
|
2016-09-01 08:59:10 -04:00
|
|
|
def resolve_conflicts(files)
|
2016-07-27 12:54:04 -04:00
|
|
|
post :resolve_conflicts,
|
|
|
|
namespace_id: merge_request_with_conflicts.project.namespace.to_param,
|
2017-02-23 18:55:01 -05:00
|
|
|
project_id: merge_request_with_conflicts.project,
|
2016-07-27 12:54:04 -04:00
|
|
|
id: merge_request_with_conflicts.iid,
|
|
|
|
format: 'json',
|
2016-09-01 08:59:10 -04:00
|
|
|
files: files,
|
2016-08-02 08:56:50 -04:00
|
|
|
commit_message: 'Commit message'
|
2016-07-27 12:54:04 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
context 'with valid params' do
|
|
|
|
before do
|
2016-09-01 08:59:10 -04:00
|
|
|
resolved_files = [
|
|
|
|
{
|
|
|
|
'new_path' => 'files/ruby/popen.rb',
|
|
|
|
'old_path' => 'files/ruby/popen.rb',
|
|
|
|
'sections' => {
|
|
|
|
'2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head'
|
|
|
|
}
|
|
|
|
}, {
|
|
|
|
'new_path' => 'files/ruby/regex.rb',
|
|
|
|
'old_path' => 'files/ruby/regex.rb',
|
|
|
|
'sections' => {
|
|
|
|
'6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head',
|
|
|
|
'6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin',
|
|
|
|
'6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
|
|
|
|
resolve_conflicts(resolved_files)
|
2016-07-27 12:54:04 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'creates a new commit on the branch' do
|
2016-07-29 09:51:11 -04:00
|
|
|
expect(original_head_sha).not_to eq(merge_request_with_conflicts.source_branch_head.sha)
|
2016-08-02 08:56:50 -04:00
|
|
|
expect(merge_request_with_conflicts.source_branch_head.message).to include('Commit message')
|
2016-07-27 12:54:04 -04:00
|
|
|
end
|
|
|
|
|
2016-08-05 07:15:06 -04:00
|
|
|
it 'returns an OK response' do
|
2016-08-02 08:56:50 -04:00
|
|
|
expect(response).to have_http_status(:ok)
|
2016-07-27 12:54:04 -04:00
|
|
|
end
|
|
|
|
end
|
2016-07-29 09:51:11 -04:00
|
|
|
|
|
|
|
context 'when sections are missing' do
|
|
|
|
before do
|
2016-09-01 08:59:10 -04:00
|
|
|
resolved_files = [
|
|
|
|
{
|
|
|
|
'new_path' => 'files/ruby/popen.rb',
|
|
|
|
'old_path' => 'files/ruby/popen.rb',
|
|
|
|
'sections' => {
|
|
|
|
'2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head'
|
|
|
|
}
|
|
|
|
}, {
|
|
|
|
'new_path' => 'files/ruby/regex.rb',
|
|
|
|
'old_path' => 'files/ruby/regex.rb',
|
|
|
|
'sections' => {
|
|
|
|
'6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
|
|
|
|
resolve_conflicts(resolved_files)
|
2016-07-29 09:51:11 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns a 400 error' do
|
|
|
|
expect(response).to have_http_status(:bad_request)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'has a message with the name of the first missing section' do
|
2016-09-01 08:59:10 -04:00
|
|
|
expect(json_response['message']).to include('6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21')
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'does not create a new commit' do
|
|
|
|
expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when files are missing' do
|
|
|
|
before do
|
|
|
|
resolved_files = [
|
|
|
|
{
|
|
|
|
'new_path' => 'files/ruby/regex.rb',
|
|
|
|
'old_path' => 'files/ruby/regex.rb',
|
|
|
|
'sections' => {
|
|
|
|
'6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head',
|
|
|
|
'6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin',
|
|
|
|
'6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
|
|
|
|
resolve_conflicts(resolved_files)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns a 400 error' do
|
|
|
|
expect(response).to have_http_status(:bad_request)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'has a message with the name of the missing file' do
|
|
|
|
expect(json_response['message']).to include('files/ruby/popen.rb')
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'does not create a new commit' do
|
|
|
|
expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when a file has identical content to the conflict' do
|
|
|
|
before do
|
2017-05-11 11:23:02 -04:00
|
|
|
content = MergeRequests::Conflicts::ListService.new(merge_request_with_conflicts).
|
|
|
|
file_for_path('files/ruby/popen.rb', 'files/ruby/popen.rb').
|
|
|
|
content
|
|
|
|
|
2016-09-01 08:59:10 -04:00
|
|
|
resolved_files = [
|
|
|
|
{
|
|
|
|
'new_path' => 'files/ruby/popen.rb',
|
|
|
|
'old_path' => 'files/ruby/popen.rb',
|
2017-05-11 11:23:02 -04:00
|
|
|
'content' => content
|
2016-09-01 08:59:10 -04:00
|
|
|
}, {
|
|
|
|
'new_path' => 'files/ruby/regex.rb',
|
|
|
|
'old_path' => 'files/ruby/regex.rb',
|
|
|
|
'sections' => {
|
|
|
|
'6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head',
|
|
|
|
'6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin',
|
|
|
|
'6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
|
|
|
|
resolve_conflicts(resolved_files)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns a 400 error' do
|
|
|
|
expect(response).to have_http_status(:bad_request)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'has a message with the path of the problem file' do
|
|
|
|
expect(json_response['message']).to include('files/ruby/popen.rb')
|
2016-07-29 09:51:11 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'does not create a new commit' do
|
|
|
|
expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha)
|
|
|
|
end
|
|
|
|
end
|
2016-07-27 12:54:04 -04:00
|
|
|
end
|
2016-08-08 18:30:01 -04:00
|
|
|
|
|
|
|
describe 'POST assign_related_issues' do
|
|
|
|
let(:issue1) { create(:issue, project: project) }
|
|
|
|
let(:issue2) { create(:issue, project: project) }
|
|
|
|
|
|
|
|
def post_assign_issues
|
|
|
|
merge_request.update!(description: "Closes #{issue1.to_reference} and #{issue2.to_reference}",
|
|
|
|
author: user,
|
|
|
|
source_branch: 'feature',
|
|
|
|
target_branch: 'master')
|
|
|
|
|
|
|
|
post :assign_related_issues,
|
|
|
|
namespace_id: project.namespace.to_param,
|
2017-02-23 18:55:01 -05:00
|
|
|
project_id: project,
|
2016-08-08 18:30:01 -04:00
|
|
|
id: merge_request.iid
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'shows a flash message on success' do
|
|
|
|
post_assign_issues
|
|
|
|
|
|
|
|
expect(flash[:notice]).to eq '2 issues have been assigned to you'
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'correctly pluralizes flash message on success' do
|
2017-05-04 08:11:15 -04:00
|
|
|
issue2.assignees = [user]
|
2016-08-08 18:30:01 -04:00
|
|
|
|
|
|
|
post_assign_issues
|
|
|
|
|
|
|
|
expect(flash[:notice]).to eq '1 issue has been assigned to you'
|
|
|
|
end
|
2016-10-07 12:16:42 -04:00
|
|
|
|
|
|
|
it 'calls MergeRequests::AssignIssuesService' do
|
2017-02-22 17:50:15 -05:00
|
|
|
expect(MergeRequests::AssignIssuesService).to receive(:new).
|
|
|
|
with(project, user, merge_request: merge_request).
|
|
|
|
and_return(double(execute: { count: 1 }))
|
2016-10-07 12:16:42 -04:00
|
|
|
|
|
|
|
post_assign_issues
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'is skipped when not signed in' do
|
|
|
|
project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
|
|
|
|
sign_out(:user)
|
|
|
|
|
|
|
|
expect(MergeRequests::AssignIssuesService).not_to receive(:new)
|
|
|
|
|
|
|
|
post_assign_issues
|
|
|
|
end
|
2016-08-08 18:30:01 -04:00
|
|
|
end
|
2016-10-13 08:23:18 -04:00
|
|
|
|
|
|
|
describe 'GET ci_environments_status' do
|
2016-10-19 08:49:09 -04:00
|
|
|
context 'the environment is from a forked project' do
|
2016-10-13 08:23:18 -04:00
|
|
|
let!(:forked) { create(:project) }
|
|
|
|
let!(:environment) { create(:environment, project: forked) }
|
|
|
|
let!(:deployment) { create(:deployment, environment: environment, sha: forked.commit.id, ref: 'master') }
|
|
|
|
let(:admin) { create(:admin) }
|
|
|
|
|
|
|
|
let(:merge_request) do
|
|
|
|
create(:forked_project_link, forked_to_project: forked,
|
|
|
|
forked_from_project: project)
|
|
|
|
|
|
|
|
create(:merge_request, source_project: forked, target_project: project)
|
|
|
|
end
|
|
|
|
|
|
|
|
before do
|
|
|
|
forked.team << [user, :master]
|
|
|
|
|
|
|
|
get :ci_environments_status,
|
|
|
|
namespace_id: merge_request.project.namespace.to_param,
|
2017-02-23 18:55:01 -05:00
|
|
|
project_id: merge_request.project,
|
2016-10-13 08:23:18 -04:00
|
|
|
id: merge_request.iid, format: 'json'
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'links to the environment on that project' do
|
|
|
|
expect(json_response.first['url']).to match /#{forked.path_with_namespace}/
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2016-12-21 05:32:37 -05:00
|
|
|
|
2017-03-17 02:58:12 -04:00
|
|
|
describe 'GET pipeline_status.json' do
|
2017-03-21 09:21:13 -04:00
|
|
|
context 'when head_pipeline exists' do
|
|
|
|
let!(:pipeline) do
|
2017-03-06 07:01:18 -05:00
|
|
|
create(:ci_pipeline, project: merge_request.source_project,
|
2017-03-06 10:42:39 -05:00
|
|
|
ref: merge_request.source_branch,
|
2017-05-19 16:51:07 -04:00
|
|
|
sha: merge_request.diff_head_sha,
|
|
|
|
head_pipeline_of: merge_request)
|
2017-03-06 07:01:18 -05:00
|
|
|
end
|
|
|
|
|
2017-03-21 09:21:13 -04:00
|
|
|
let(:status) { pipeline.detailed_status(double('user')) }
|
|
|
|
|
2017-03-28 16:04:14 -04:00
|
|
|
before do
|
|
|
|
get_pipeline_status
|
|
|
|
end
|
2017-03-21 09:21:13 -04:00
|
|
|
|
|
|
|
it 'return a detailed head_pipeline status in json' do
|
2017-03-06 07:01:18 -05:00
|
|
|
expect(response).to have_http_status(:ok)
|
2017-03-11 09:30:25 -05:00
|
|
|
expect(json_response['text']).to eq status.text
|
|
|
|
expect(json_response['label']).to eq status.label
|
|
|
|
expect(json_response['icon']).to eq status.icon
|
2017-04-17 08:18:40 -04:00
|
|
|
expect(json_response['favicon']).to eq "/assets/ci_favicons/#{status.favicon}.ico"
|
2017-03-06 07:01:18 -05:00
|
|
|
end
|
|
|
|
end
|
2017-03-21 09:21:13 -04:00
|
|
|
|
|
|
|
context 'when head_pipeline does not exist' do
|
|
|
|
before { get_pipeline_status }
|
|
|
|
|
|
|
|
it 'return empty' do
|
|
|
|
expect(response).to have_http_status(:ok)
|
|
|
|
expect(json_response).to be_empty
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def get_pipeline_status
|
|
|
|
get :pipeline_status, namespace_id: project.namespace,
|
|
|
|
project_id: project,
|
|
|
|
id: merge_request.iid,
|
|
|
|
format: :json
|
|
|
|
end
|
2017-03-06 07:01:18 -05:00
|
|
|
end
|
2012-11-24 18:04:13 -05:00
|
|
|
end
|