2012-11-24 18:04:13 -05:00
|
|
|
require 'spec_helper'
|
|
|
|
|
2013-06-23 13:25:06 -04:00
|
|
|
describe Projects::MergeRequestsController do
|
2017-09-29 04:04:50 -04:00
|
|
|
include ProjectForksHelper
|
|
|
|
|
2017-08-01 14:51:52 -04:00
|
|
|
let(:project) { create(:project, :repository) }
|
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
|
2018-03-27 23:20:12 -04:00
|
|
|
create(:merge_request, source_branch: 'conflict-resolvable', target_branch: 'conflict-start', source_project: project, merge_status: :unchecked) 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
|
|
|
|
|
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,
|
2018-12-17 17:52:17 -05:00
|
|
|
params: {
|
|
|
|
namespace_id: project.namespace.to_param,
|
|
|
|
project_id: project,
|
|
|
|
id: merge_request.iid
|
|
|
|
},
|
2017-05-09 00:15:34 -04:00
|
|
|
format: 'html'
|
|
|
|
|
|
|
|
expect(response).to render_template('_commit_change_content')
|
|
|
|
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
|
|
|
|
2018-12-17 17:52:17 -05:00
|
|
|
get :show, params: params.merge(extra_params)
|
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 html' do
|
2018-07-17 16:19:22 -04:00
|
|
|
context 'when diff files were cleaned' do
|
|
|
|
render_views
|
|
|
|
|
|
|
|
it 'renders page when diff size is not persisted and diff_refs does not exist' do
|
|
|
|
diff = merge_request.merge_request_diff
|
|
|
|
|
|
|
|
diff.clean!
|
|
|
|
diff.update!(real_size: nil,
|
|
|
|
start_commit_sha: nil,
|
|
|
|
base_commit_sha: nil)
|
|
|
|
|
|
|
|
go(format: :html)
|
|
|
|
|
|
|
|
expect(response).to be_success
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-05-09 00:15:34 -04:00
|
|
|
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-08-29 09:46:40 -04:00
|
|
|
|
2018-06-05 07:42:18 -04:00
|
|
|
context "that is invalid" do
|
|
|
|
let(:merge_request) { create(:invalid_merge_request, target_project: project, source_project: project) }
|
|
|
|
|
|
|
|
it "renders merge request page" do
|
|
|
|
go(format: :html)
|
|
|
|
|
|
|
|
expect(response).to be_success
|
|
|
|
end
|
|
|
|
end
|
2017-05-09 00:15:34 -04:00
|
|
|
end
|
2012-11-24 18:04:13 -05:00
|
|
|
|
2018-10-23 05:49:45 -04:00
|
|
|
context 'when user is setting notes filters' do
|
|
|
|
let(:issuable) { merge_request }
|
|
|
|
let!(:discussion_note) { create(:discussion_note_on_merge_request, :system, noteable: issuable, project: project) }
|
|
|
|
let!(:discussion_comment) { create(:discussion_note_on_merge_request, noteable: issuable, project: project) }
|
|
|
|
|
|
|
|
it_behaves_like 'issuable notes filter'
|
|
|
|
end
|
|
|
|
|
2017-05-09 00:15:34 -04:00
|
|
|
describe 'as json' do
|
2017-10-31 12:15:03 -04:00
|
|
|
context 'with basic serializer param' do
|
2017-05-09 00:15:34 -04:00
|
|
|
it 'renders basic MR entity as json' do
|
2017-10-31 12:15:03 -04:00
|
|
|
go(serializer: 'basic', 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-12-07 16:56:59 -05:00
|
|
|
context 'with widget serializer param' do
|
|
|
|
it 'renders widget MR entity as json' do
|
|
|
|
go(serializer: 'widget', format: :json)
|
2012-11-24 18:04:13 -05:00
|
|
|
|
2017-12-07 16:56:59 -05:00
|
|
|
expect(response).to match_response_schema('entities/merge_request_widget')
|
2017-05-09 00:15:34 -04:00
|
|
|
end
|
2012-11-24 18:04:13 -05:00
|
|
|
end
|
2017-12-21 08:03:15 -05:00
|
|
|
|
|
|
|
context 'when no serialiser was passed' do
|
|
|
|
it 'renders widget MR entity as json' do
|
|
|
|
go(serializer: nil, format: :json)
|
|
|
|
|
|
|
|
expect(response).to match_response_schema('entities/merge_request_widget')
|
|
|
|
end
|
|
|
|
end
|
2018-06-05 07:42:18 -04:00
|
|
|
|
|
|
|
context "that is invalid" do
|
|
|
|
let(:merge_request) { create(:invalid_merge_request, target_project: project, source_project: project) }
|
|
|
|
|
|
|
|
it "renders merge request page" do
|
|
|
|
go(format: :json)
|
|
|
|
|
|
|
|
expect(response).to be_success
|
|
|
|
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-08-01 12:55:57 -04:00
|
|
|
let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
|
2017-01-23 15:40:25 -05:00
|
|
|
|
2016-12-20 13:52:09 -05:00
|
|
|
def get_merge_requests(page = nil)
|
2016-02-17 21:15:13 -05:00
|
|
|
get :index,
|
2018-12-17 17:52:17 -05:00
|
|
|
params: {
|
|
|
|
namespace_id: project.namespace.to_param,
|
|
|
|
project_id: project,
|
|
|
|
state: 'opened',
|
|
|
|
page: page.to_param
|
|
|
|
}
|
2016-12-20 13:52:09 -05:00
|
|
|
end
|
|
|
|
|
2017-01-23 15:40:25 -05:00
|
|
|
it_behaves_like "issuables list meta-data", :merge_request
|
|
|
|
|
2018-12-12 11:15:58 -05:00
|
|
|
it_behaves_like 'set sort order from user preference'
|
|
|
|
|
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)
|
2017-10-19 14:28:19 -04:00
|
|
|
expect(response).to have_gitlab_http_status(200)
|
2016-12-20 13:52:09 -05:00
|
|
|
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,
|
2018-12-17 17:52:17 -05:00
|
|
|
params: {
|
|
|
|
namespace_id: project.namespace.to_param,
|
|
|
|
project_id: project,
|
|
|
|
state: 'opened',
|
|
|
|
page: (last_page + 1).to_param,
|
|
|
|
host: external_host
|
|
|
|
}
|
2017-04-05 18:52:19 -04:00
|
|
|
|
|
|
|
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
|
2017-08-01 12:55:57 -04:00
|
|
|
expect(merge_request).to be_persisted
|
|
|
|
|
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-11-01 13:35:14 -04:00
|
|
|
def update_merge_request(mr_params, additional_params = {})
|
|
|
|
params = {
|
|
|
|
namespace_id: project.namespace,
|
|
|
|
project_id: project,
|
|
|
|
id: merge_request.iid,
|
|
|
|
merge_request: mr_params
|
|
|
|
}.merge(additional_params)
|
|
|
|
|
2018-12-17 17:52:17 -05:00
|
|
|
put :update, params: params
|
2017-11-01 13:35:14 -04:00
|
|
|
end
|
|
|
|
|
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)
|
|
|
|
|
2017-11-01 13:35:14 -04:00
|
|
|
update_merge_request({ assignee_id: assignee.id }, format: :json)
|
2017-03-18 00:23:15 -04:00
|
|
|
body = JSON.parse(response.body)
|
|
|
|
|
|
|
|
expect(body['assignee'].keys)
|
2018-06-14 15:39:17 -04:00
|
|
|
.to match_array(%w(name username avatar_url id state web_url))
|
2017-03-18 00:23:15 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-11-01 13:35:14 -04:00
|
|
|
context 'when user does not have access to update issue' do
|
|
|
|
before do
|
|
|
|
reporter = create(:user)
|
|
|
|
project.add_reporter(reporter)
|
|
|
|
sign_in(reporter)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'responds with 404' do
|
|
|
|
update_merge_request(title: 'New title')
|
|
|
|
|
|
|
|
expect(response).to have_http_status(:not_found)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-04-11 15:21:32 -04:00
|
|
|
context 'there is no source project' do
|
2017-08-01 14:51:52 -04:00
|
|
|
let(:project) { create(:project, :repository) }
|
2017-09-29 04:04:50 -04:00
|
|
|
let(:forked_project) { fork_project_with_submodules(project) }
|
|
|
|
let!(:merge_request) { create(:merge_request, source_project: forked_project, source_branch: 'add-submodule-version-bump', target_branch: 'master', target_project: project) }
|
2016-04-11 15:21:32 -04:00
|
|
|
|
|
|
|
before do
|
2017-09-29 04:04:50 -04:00
|
|
|
forked_project.destroy
|
2016-04-11 15:21:32 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'closes MR without errors' do
|
2017-11-01 13:35:14 -04:00
|
|
|
update_merge_request(state_event: 'close')
|
2016-04-11 15:21:32 -04:00
|
|
|
|
|
|
|
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!
|
|
|
|
|
2017-11-01 13:35:14 -04:00
|
|
|
update_merge_request(title: 'New title')
|
2016-07-26 07:57:43 -04:00
|
|
|
|
|
|
|
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!
|
|
|
|
|
2017-11-01 13:35:14 -04:00
|
|
|
update_merge_request(target_branch: 'new_branch')
|
2016-07-26 07:57:43 -04:00
|
|
|
|
|
|
|
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
|
2018-01-13 06:06:03 -05:00
|
|
|
|
2018-10-12 11:06:56 -04:00
|
|
|
context 'two merge requests with the same source branch' do
|
|
|
|
it 'does not allow a closed merge request to be reopened if another one is open' do
|
2018-01-13 06:06:03 -05:00
|
|
|
merge_request.close!
|
|
|
|
create(:merge_request, source_project: merge_request.source_project, source_branch: merge_request.source_branch)
|
|
|
|
|
|
|
|
update_merge_request(state_event: 'reopen')
|
|
|
|
|
2018-10-12 11:06:56 -04:00
|
|
|
errors = assigns[:merge_request].errors
|
|
|
|
|
|
|
|
expect(errors[:validate_branches]).to include(/Another open merge request already exists for this source branch/)
|
2018-01-13 06:06:03 -05:00
|
|
|
expect(merge_request.reload).to be_closed
|
|
|
|
end
|
|
|
|
end
|
2016-04-11 15:21:32 -04:00
|
|
|
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,
|
2018-05-29 05:51:43 -04:00
|
|
|
squash: false,
|
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)
|
2018-12-19 16:57:41 -05:00
|
|
|
post :merge, params: base_params, xhr: true
|
2016-06-01 08:25:44 -04:00
|
|
|
end
|
|
|
|
|
2017-05-09 00:15:34 -04:00
|
|
|
it 'returns 404' do
|
2017-10-19 14:28:19 -04:00
|
|
|
expect(response).to have_gitlab_http_status(404)
|
2016-06-01 08:25:44 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the merge request is not mergeable' do
|
|
|
|
before do
|
2018-07-02 06:43:06 -04:00
|
|
|
merge_request.update(title: "WIP: #{merge_request.title}")
|
2016-06-01 08:25:44 -04:00
|
|
|
|
2018-12-17 17:52:17 -05:00
|
|
|
post :merge, params: base_params
|
2016-06-01 08:25:44 -04:00
|
|
|
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
|
2017-06-14 14:18:56 -04:00
|
|
|
before do
|
2018-12-17 17:52:17 -05:00
|
|
|
post :merge, params: base_params.merge(sha: 'foo')
|
2017-06-14 14:18:56 -04:00
|
|
|
end
|
2016-06-01 08:25:44 -04:00
|
|
|
|
|
|
|
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
|
2018-05-29 05:51:43 -04:00
|
|
|
def merge_with_sha(params = {})
|
2018-06-19 12:33:18 -04:00
|
|
|
post_params = base_params.merge(sha: merge_request.diff_head_sha).merge(params)
|
2018-12-15 04:06:56 -05:00
|
|
|
post :merge, params: post_params, as: :json
|
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
|
|
|
|
|
2018-05-30 12:18:04 -04:00
|
|
|
it 'starts the merge immediately with permitted params' do
|
|
|
|
expect(MergeWorker).to receive(:perform_async).with(merge_request.id, anything, { 'squash' => false })
|
2016-06-01 08:25:44 -04:00
|
|
|
|
|
|
|
merge_with_sha
|
|
|
|
end
|
|
|
|
|
2018-05-29 05:51:43 -04:00
|
|
|
context 'when squash is passed as 1' do
|
|
|
|
it 'updates the squash attribute on the MR to true' do
|
|
|
|
merge_request.update(squash: false)
|
|
|
|
merge_with_sha(squash: '1')
|
|
|
|
|
|
|
|
expect(merge_request.reload.squash).to be_truthy
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when squash is passed as 0' do
|
|
|
|
it 'updates the squash attribute on the MR to false' do
|
|
|
|
merge_request.update(squash: true)
|
|
|
|
merge_with_sha(squash: '0')
|
|
|
|
|
|
|
|
expect(merge_request.reload.squash).to be_falsey
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-02-17 08:56:13 -05:00
|
|
|
context 'when the pipeline succeeds is passed' do
|
2017-11-30 13:00:09 -05:00
|
|
|
let!(:head_pipeline) do
|
|
|
|
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-11-30 13:00:09 -05:00
|
|
|
def merge_when_pipeline_succeeds
|
2018-12-17 17:52:17 -05:00
|
|
|
post :merge, params: base_params.merge(sha: merge_request.diff_head_sha, merge_when_pipeline_succeeds: '1')
|
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-11-30 13:00:09 -05:00
|
|
|
context 'and head pipeline is not the current one' do
|
|
|
|
before do
|
|
|
|
head_pipeline.update(sha: 'not_current_sha')
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns :failed' do
|
|
|
|
merge_when_pipeline_succeeds
|
|
|
|
|
|
|
|
expect(json_response).to eq('status' => 'failed')
|
|
|
|
end
|
|
|
|
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
|
2018-12-17 17:52:17 -05:00
|
|
|
delete :destroy, params: { namespace_id: project.namespace, project_id: project, id: merge_request.iid }
|
2016-02-26 03:55:43 -05:00
|
|
|
|
2017-10-19 14:28:19 -04:00
|
|
|
expect(response).to have_gitlab_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) }
|
2017-08-01 14:51:52 -04:00
|
|
|
let(:project) { create(:project, :repository, namespace: namespace) }
|
2016-03-21 09:12:52 -04:00
|
|
|
|
2017-06-14 14:18:56 -04:00
|
|
|
before do
|
|
|
|
sign_in owner
|
|
|
|
end
|
2016-02-26 03:55:43 -05:00
|
|
|
|
2016-03-21 09:12:52 -04:00
|
|
|
it "deletes the merge request" do
|
2018-12-17 17:52:17 -05:00
|
|
|
delete :destroy, params: { namespace_id: project.namespace, project_id: project, id: merge_request.iid }
|
2016-02-26 03:55:43 -05:00
|
|
|
|
2017-10-19 14:28:19 -04:00
|
|
|
expect(response).to have_gitlab_http_status(302)
|
2017-07-24 08:21:16 -04:00
|
|
|
expect(controller).to set_flash[:notice].to(/The merge request was successfully deleted\./)
|
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
|
2017-12-08 07:17:22 -05:00
|
|
|
expect_any_instance_of(TodoService).to receive(:destroy_target).with(merge_request).once
|
2016-09-01 18:12:05 -04:00
|
|
|
|
2018-12-17 17:52:17 -05:00
|
|
|
delete :destroy, params: { 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 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,
|
2018-12-17 17:52:17 -05:00
|
|
|
params: {
|
|
|
|
namespace_id: project.namespace.to_param,
|
|
|
|
project_id: project,
|
|
|
|
id: merge_request.iid
|
|
|
|
},
|
2015-06-23 01:24:39 -04:00
|
|
|
format: format
|
2015-06-17 16:12:28 -04:00
|
|
|
end
|
|
|
|
|
2017-06-13 18:12:31 -04:00
|
|
|
it 'renders the commits template to a string' do
|
|
|
|
go format: 'json'
|
2015-06-17 16:12:28 -04:00
|
|
|
|
2017-06-13 18:12:31 -04:00
|
|
|
expect(response).to render_template('projects/merge_requests/_commits')
|
|
|
|
expect(json_response).to have_key('html')
|
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)
|
2016-07-29 09:51:11 -04:00
|
|
|
|
2017-06-13 18:12:31 -04:00
|
|
|
get :pipelines,
|
2018-12-17 17:52:17 -05:00
|
|
|
params: {
|
|
|
|
namespace_id: project.namespace.to_param,
|
|
|
|
project_id: project,
|
|
|
|
id: merge_request.iid
|
|
|
|
},
|
2017-06-13 18:12:31 -04:00
|
|
|
format: :json
|
2016-07-29 09:51:11 -04:00
|
|
|
end
|
|
|
|
|
2017-06-13 18:12:31 -04:00
|
|
|
it 'responds with serialized pipelines' do
|
2017-07-14 11:52:54 -04:00
|
|
|
expect(json_response['pipelines']).not_to be_empty
|
|
|
|
expect(json_response['count']['all']).to eq 1
|
2018-10-30 11:56:30 -04:00
|
|
|
expect(response).to include_pagination_headers
|
2016-07-27 07:42:18 -04:00
|
|
|
end
|
|
|
|
end
|
2016-07-27 12:54:04 -04:00
|
|
|
|
2018-08-02 02:05:07 -04:00
|
|
|
describe 'GET test_reports' do
|
|
|
|
subject do
|
|
|
|
get :test_reports,
|
2018-12-17 17:52:17 -05:00
|
|
|
params: {
|
|
|
|
namespace_id: project.namespace.to_param,
|
|
|
|
project_id: project,
|
|
|
|
id: merge_request.iid
|
|
|
|
},
|
2018-08-02 02:05:07 -04:00
|
|
|
format: :json
|
|
|
|
end
|
|
|
|
|
|
|
|
before do
|
|
|
|
allow_any_instance_of(MergeRequest)
|
|
|
|
.to receive(:compare_test_reports).and_return(comparison_status)
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when comparison is being processed' do
|
|
|
|
let(:comparison_status) { { status: :parsing } }
|
|
|
|
|
2018-08-07 10:02:57 -04:00
|
|
|
it 'sends polling interval' do
|
|
|
|
expect(Gitlab::PollingInterval).to receive(:set_header)
|
|
|
|
|
|
|
|
subject
|
|
|
|
end
|
|
|
|
|
2018-08-02 02:05:07 -04:00
|
|
|
it 'returns 204 HTTP status' do
|
|
|
|
subject
|
|
|
|
|
|
|
|
expect(response).to have_gitlab_http_status(:no_content)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when comparison is done' do
|
2018-08-03 05:38:29 -04:00
|
|
|
let(:comparison_status) { { status: :parsed, data: { summary: 1 } } }
|
2018-08-02 02:05:07 -04:00
|
|
|
|
2018-08-07 10:02:57 -04:00
|
|
|
it 'does not send polling interval' do
|
|
|
|
expect(Gitlab::PollingInterval).not_to receive(:set_header)
|
|
|
|
|
|
|
|
subject
|
|
|
|
end
|
|
|
|
|
2018-08-02 02:05:07 -04:00
|
|
|
it 'returns 200 HTTP status' do
|
|
|
|
subject
|
|
|
|
|
|
|
|
expect(response).to have_gitlab_http_status(:ok)
|
|
|
|
expect(json_response).to eq({ 'summary' => 1 })
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when user created corrupted test reports' do
|
|
|
|
let(:comparison_status) { { status: :error, status_reason: 'Failed to parse test reports' } }
|
|
|
|
|
2018-08-07 10:02:57 -04:00
|
|
|
it 'does not send polling interval' do
|
|
|
|
expect(Gitlab::PollingInterval).not_to receive(:set_header)
|
|
|
|
|
|
|
|
subject
|
|
|
|
end
|
|
|
|
|
2018-08-02 02:05:07 -04:00
|
|
|
it 'returns 400 HTTP status' do
|
|
|
|
subject
|
|
|
|
|
|
|
|
expect(response).to have_gitlab_http_status(:bad_request)
|
|
|
|
expect(json_response).to eq({ 'status_reason' => 'Failed to parse test reports' })
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when something went wrong on our system' do
|
|
|
|
let(:comparison_status) { {} }
|
|
|
|
|
2018-08-07 10:02:57 -04:00
|
|
|
it 'does not send polling interval' do
|
|
|
|
expect(Gitlab::PollingInterval).not_to receive(:set_header)
|
|
|
|
|
|
|
|
subject
|
|
|
|
end
|
|
|
|
|
2018-08-02 02:05:07 -04:00
|
|
|
it 'returns 500 HTTP status' do
|
|
|
|
subject
|
|
|
|
|
|
|
|
expect(response).to have_gitlab_http_status(:internal_server_error)
|
|
|
|
expect(json_response).to eq({ 'status_reason' => 'Unknown error' })
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
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
|
|
|
|
|
2018-12-19 16:57:41 -05:00
|
|
|
post :remove_wip,
|
2018-12-17 17:52:17 -05:00
|
|
|
params: {
|
2018-12-19 16:57:41 -05:00
|
|
|
format: :json,
|
2018-12-17 17:52:17 -05:00
|
|
|
namespace_id: merge_request.project.namespace.to_param,
|
|
|
|
project_id: merge_request.project,
|
|
|
|
id: merge_request.iid
|
2018-12-19 16:57:41 -05:00
|
|
|
},
|
|
|
|
xhr: true
|
2017-05-09 00:15:34 -04:00
|
|
|
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
|
2018-12-19 16:57:41 -05:00
|
|
|
post :cancel_merge_when_pipeline_succeeds,
|
2018-12-17 17:52:17 -05:00
|
|
|
params: {
|
2018-12-19 16:57:41 -05:00
|
|
|
format: :json,
|
2018-12-17 17:52:17 -05:00
|
|
|
namespace_id: merge_request.project.namespace.to_param,
|
|
|
|
project_id: merge_request.project,
|
|
|
|
id: merge_request.iid
|
2018-12-19 16:57:41 -05:00
|
|
|
},
|
|
|
|
xhr: true
|
2017-05-09 00:15:34 -04:00
|
|
|
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
|
|
|
|
|
2017-10-19 14:28:19 -04:00
|
|
|
it { is_expected.to have_gitlab_http_status(:success) }
|
2017-05-09 00:15:34 -04:00
|
|
|
|
|
|
|
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
|
|
|
|
|
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,
|
2018-12-17 17:52:17 -05:00
|
|
|
params: {
|
|
|
|
namespace_id: project.namespace.to_param,
|
|
|
|
project_id: project,
|
|
|
|
id: merge_request.iid
|
|
|
|
}
|
2016-08-08 18:30:01 -04:00
|
|
|
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-06-21 09:48:12 -04: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
|
2018-10-29 07:34:41 -04:00
|
|
|
let(:forked) { fork_project(project, user, repository: true) }
|
|
|
|
let(:sha) { forked.commit.sha }
|
|
|
|
let(:environment) { create(:environment, project: forked) }
|
|
|
|
let(:pipeline) { create(:ci_pipeline, sha: sha, project: forked) }
|
|
|
|
let(:build) { create(:ci_build, pipeline: pipeline) }
|
2018-11-04 19:37:40 -05:00
|
|
|
let!(:deployment) { create(:deployment, :succeed, environment: environment, sha: sha, ref: 'master', deployable: build) }
|
2016-10-13 08:23:18 -04:00
|
|
|
|
|
|
|
let(:merge_request) do
|
2018-10-29 07:34:41 -04:00
|
|
|
create(:merge_request, source_project: forked, target_project: project, target_branch: 'master', head_pipeline: pipeline)
|
2016-10-13 08:23:18 -04:00
|
|
|
end
|
|
|
|
|
2018-10-03 12:39:42 -04:00
|
|
|
it 'links to the environment on that project' do
|
|
|
|
get_ci_environments_status
|
|
|
|
|
|
|
|
expect(json_response.first['url']).to match /#{forked.full_path}/
|
|
|
|
end
|
|
|
|
|
2018-10-29 07:34:41 -04:00
|
|
|
context "when environment_target is 'merge_commit'" do
|
|
|
|
it 'returns nothing' do
|
|
|
|
get_ci_environments_status(environment_target: 'merge_commit')
|
|
|
|
|
|
|
|
expect(response).to have_gitlab_http_status(:ok)
|
|
|
|
expect(json_response).to be_empty
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when is merged' do
|
|
|
|
let(:source_environment) { create(:environment, project: project) }
|
|
|
|
let(:merge_commit_sha) { project.repository.merge(user, forked.commit.id, merge_request, "merged in test") }
|
|
|
|
let(:post_merge_pipeline) { create(:ci_pipeline, sha: merge_commit_sha, project: project) }
|
|
|
|
let(:post_merge_build) { create(:ci_build, pipeline: post_merge_pipeline) }
|
2018-11-04 19:37:40 -05:00
|
|
|
let!(:source_deployment) { create(:deployment, :succeed, environment: source_environment, sha: merge_commit_sha, ref: 'master', deployable: post_merge_build) }
|
2018-10-29 07:34:41 -04:00
|
|
|
|
|
|
|
before do
|
|
|
|
merge_request.update!(merge_commit_sha: merge_commit_sha)
|
|
|
|
merge_request.mark_as_merged!
|
|
|
|
end
|
|
|
|
|
2018-10-30 06:53:01 -04:00
|
|
|
it 'returns the environment on the source project' do
|
2018-10-29 07:34:41 -04:00
|
|
|
get_ci_environments_status(environment_target: 'merge_commit')
|
|
|
|
|
|
|
|
expect(response).to have_gitlab_http_status(:ok)
|
|
|
|
expect(json_response.first['url']).to match /#{project.full_path}/
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-10-03 12:39:42 -04:00
|
|
|
# we're trying to reduce the overall number of queries for this method.
|
2018-10-05 14:47:55 -04:00
|
|
|
# set a hard limit for now. https://gitlab.com/gitlab-org/gitlab-ce/issues/52287
|
2018-10-03 12:39:42 -04:00
|
|
|
it 'keeps queries in check' do
|
|
|
|
control_count = ActiveRecord::QueryRecorder.new { get_ci_environments_status }.count
|
|
|
|
|
|
|
|
expect(control_count).to be <= 137
|
|
|
|
end
|
|
|
|
|
2018-10-29 07:34:41 -04:00
|
|
|
def get_ci_environments_status(extra_params = {})
|
|
|
|
params = {
|
2016-10-13 08:23:18 -04:00
|
|
|
namespace_id: merge_request.project.namespace.to_param,
|
2017-02-23 18:55:01 -05:00
|
|
|
project_id: merge_request.project,
|
2018-10-29 07:34:41 -04:00
|
|
|
id: merge_request.iid,
|
|
|
|
format: 'json'
|
|
|
|
}
|
|
|
|
|
2018-12-17 17:52:17 -05:00
|
|
|
get :ci_environments_status, params: params.merge(extra_params)
|
2016-10-13 08:23:18 -04:00
|
|
|
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-10-19 14:28:19 -04:00
|
|
|
expect(response).to have_gitlab_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-12-07 07:15:49 -05:00
|
|
|
expect(json_response['favicon']).to match_asset_path "/assets/ci_favicons/#{status.favicon}.png"
|
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
|
2017-06-14 14:18:56 -04:00
|
|
|
before do
|
|
|
|
get_pipeline_status
|
|
|
|
end
|
2017-03-21 09:21:13 -04:00
|
|
|
|
|
|
|
it 'return empty' do
|
2017-10-19 14:28:19 -04:00
|
|
|
expect(response).to have_gitlab_http_status(:ok)
|
2017-03-21 09:21:13 -04:00
|
|
|
expect(json_response).to be_empty
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def get_pipeline_status
|
2018-12-17 17:52:17 -05:00
|
|
|
get :pipeline_status, params: {
|
|
|
|
namespace_id: project.namespace,
|
|
|
|
project_id: project,
|
|
|
|
id: merge_request.iid
|
|
|
|
},
|
2017-03-21 09:21:13 -04:00
|
|
|
format: :json
|
|
|
|
end
|
2017-03-06 07:01:18 -05:00
|
|
|
end
|
2017-12-20 04:01:21 -05:00
|
|
|
|
|
|
|
describe 'POST #rebase' do
|
|
|
|
let(:viewer) { user }
|
|
|
|
|
|
|
|
def post_rebase
|
2018-12-17 17:52:17 -05:00
|
|
|
post :rebase, params: { namespace_id: project.namespace, project_id: project, id: merge_request }
|
2017-12-20 04:01:21 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def expect_rebase_worker_for(user)
|
|
|
|
expect(RebaseWorker).to receive(:perform_async).with(merge_request.id, user.id)
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'successfully' do
|
|
|
|
it 'enqeues a RebaseWorker' do
|
|
|
|
expect_rebase_worker_for(viewer)
|
|
|
|
|
|
|
|
post_rebase
|
|
|
|
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with a forked project' do
|
2018-08-31 13:16:34 -04:00
|
|
|
let(:forked_project) { fork_project(project, fork_owner, repository: true) }
|
|
|
|
let(:fork_owner) { create(:user) }
|
2017-12-20 04:01:21 -05:00
|
|
|
|
|
|
|
before do
|
2018-08-31 13:16:34 -04:00
|
|
|
project.add_developer(fork_owner)
|
|
|
|
|
|
|
|
merge_request.update!(source_project: forked_project)
|
|
|
|
forked_project.add_reporter(user)
|
2017-12-20 04:01:21 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
context 'user cannot push to source branch' do
|
|
|
|
it 'returns 404' do
|
|
|
|
expect_rebase_worker_for(viewer).never
|
|
|
|
|
|
|
|
post_rebase
|
|
|
|
|
|
|
|
expect(response.status).to eq(404)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'user can push to source branch' do
|
|
|
|
before do
|
|
|
|
project.add_reporter(fork_owner)
|
|
|
|
|
|
|
|
sign_in(fork_owner)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns 200' do
|
|
|
|
expect_rebase_worker_for(fork_owner)
|
|
|
|
|
|
|
|
post_rebase
|
|
|
|
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2018-09-12 12:39:34 -04:00
|
|
|
|
2018-12-16 11:00:43 -05:00
|
|
|
describe 'GET discussions' do
|
|
|
|
context 'when authenticated' do
|
|
|
|
before do
|
|
|
|
project.add_developer(user)
|
|
|
|
sign_in(user)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns 200' do
|
2019-01-02 15:47:45 -05:00
|
|
|
get :discussions, params: { namespace_id: project.namespace, project_id: project, id: merge_request.iid }
|
2018-12-16 11:00:43 -05:00
|
|
|
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'highlight preloading' do
|
|
|
|
context 'with commit diff notes' do
|
|
|
|
let!(:commit_diff_note) do
|
|
|
|
create(:diff_note_on_commit, project: merge_request.project)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'preloads notes diffs highlights' do
|
|
|
|
expect_next_instance_of(Gitlab::DiscussionsDiff::FileCollection) do |collection|
|
|
|
|
note_diff_file = commit_diff_note.note_diff_file
|
|
|
|
|
|
|
|
expect(collection).to receive(:load_highlight).with([note_diff_file.id]).and_call_original
|
|
|
|
expect(collection).to receive(:find_by_id).with(note_diff_file.id).and_call_original
|
|
|
|
end
|
|
|
|
|
2019-01-02 15:47:45 -05:00
|
|
|
get :discussions, params: { namespace_id: project.namespace, project_id: project, id: merge_request.iid }
|
2018-12-16 11:00:43 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with diff notes' do
|
|
|
|
let!(:diff_note) do
|
|
|
|
create(:diff_note_on_merge_request, noteable: merge_request, project: merge_request.project)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'preloads notes diffs highlights' do
|
|
|
|
expect_next_instance_of(Gitlab::DiscussionsDiff::FileCollection) do |collection|
|
|
|
|
note_diff_file = diff_note.note_diff_file
|
|
|
|
|
|
|
|
expect(collection).to receive(:load_highlight).with([note_diff_file.id]).and_call_original
|
|
|
|
expect(collection).to receive(:find_by_id).with(note_diff_file.id).and_call_original
|
|
|
|
end
|
|
|
|
|
2019-01-02 15:47:45 -05:00
|
|
|
get :discussions, params: { namespace_id: project.namespace, project_id: project, id: merge_request.iid }
|
2018-12-16 11:00:43 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'does not preload highlights when diff note is resolved' do
|
|
|
|
Notes::ResolveService.new(diff_note.project, user).execute(diff_note)
|
|
|
|
|
|
|
|
expect_next_instance_of(Gitlab::DiscussionsDiff::FileCollection) do |collection|
|
|
|
|
note_diff_file = diff_note.note_diff_file
|
|
|
|
|
|
|
|
expect(collection).to receive(:load_highlight).with([]).and_call_original
|
|
|
|
expect(collection).to receive(:find_by_id).with(note_diff_file.id).and_call_original
|
|
|
|
end
|
|
|
|
|
2019-01-02 15:47:45 -05:00
|
|
|
get :discussions, params: { namespace_id: project.namespace, project_id: project, id: merge_request.iid }
|
2018-12-16 11:00:43 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-09-12 12:39:34 -04:00
|
|
|
describe 'GET edit' do
|
|
|
|
it 'responds successfully' do
|
2018-12-17 17:52:17 -05:00
|
|
|
get :edit, params: { namespace_id: project.namespace, project_id: project, id: merge_request }
|
2018-09-12 12:39:34 -04:00
|
|
|
|
|
|
|
expect(response).to have_gitlab_http_status(:success)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'assigns the noteable to make sure autocompletes work' do
|
2018-12-17 17:52:17 -05:00
|
|
|
get :edit, params: { namespace_id: project.namespace, project_id: project, id: merge_request }
|
2018-09-12 12:39:34 -04:00
|
|
|
|
|
|
|
expect(assigns(:noteable)).not_to be_nil
|
|
|
|
end
|
|
|
|
end
|
2012-11-24 18:04:13 -05:00
|
|
|
end
|