359 lines
11 KiB
Ruby
359 lines
11 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'spec_helper'
|
|
|
|
RSpec.describe PostReceiveService do
|
|
include Gitlab::Routing
|
|
|
|
let_it_be(:user) { create(:user) }
|
|
let_it_be(:project) { create(:project, :repository, :wiki_repo, namespace: user.namespace) }
|
|
let_it_be(:project_snippet) { create(:project_snippet, :repository, project: project, author: user) }
|
|
let_it_be(:personal_snippet) { create(:personal_snippet, :repository, author: user) }
|
|
|
|
let(:identifier) { 'key-123' }
|
|
let(:gl_repository) { "project-#{project.id}" }
|
|
let(:branch_name) { 'feature' }
|
|
let(:secret_token) { Gitlab::Shell.secret_token }
|
|
let(:reference_counter) { double('ReferenceCounter') }
|
|
let(:push_options) { ['ci.skip', 'another push option'] }
|
|
let(:repository) { project.repository }
|
|
|
|
let(:changes) do
|
|
"#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{branch_name}"
|
|
end
|
|
|
|
let(:params) do
|
|
{
|
|
gl_repository: gl_repository,
|
|
secret_token: secret_token,
|
|
identifier: identifier,
|
|
changes: changes,
|
|
push_options: push_options
|
|
}
|
|
end
|
|
|
|
let(:service) { described_class.new(user, repository, project, params) }
|
|
let(:response) { service.execute }
|
|
|
|
subject { response.messages.as_json }
|
|
|
|
context 'when project is nil' do
|
|
let(:gl_repository) { "snippet-#{personal_snippet.id}" }
|
|
let(:project) { nil }
|
|
let(:repository) { personal_snippet.repository }
|
|
|
|
it 'does not return error' do
|
|
expect(subject).to be_empty
|
|
end
|
|
|
|
it_behaves_like 'does not record an onboarding progress action'
|
|
end
|
|
|
|
context 'when repository is nil' do
|
|
let(:repository) { nil }
|
|
|
|
it 'does not return error' do
|
|
expect(subject).to be_empty
|
|
end
|
|
end
|
|
|
|
context 'when both repository and project are nil' do
|
|
let(:gl_repository) { "snippet-#{personal_snippet.id}" }
|
|
let(:project) { nil }
|
|
let(:repository) { nil }
|
|
|
|
it 'does not return error' do
|
|
expect(subject).to be_empty
|
|
end
|
|
end
|
|
|
|
shared_examples 'post_receive_service actions' do
|
|
it 'enqueues a PostReceive worker job' do
|
|
expect(PostReceive).to receive(:perform_async)
|
|
.with(gl_repository, identifier, changes, { ci: { skip: true } })
|
|
|
|
subject
|
|
end
|
|
|
|
it 'decreases the reference counter and returns the result' do
|
|
expect(Gitlab::ReferenceCounter).to receive(:new).with(gl_repository)
|
|
.and_return(reference_counter)
|
|
expect(reference_counter).to receive(:decrease).and_return(true)
|
|
|
|
expect(response.reference_counter_decreased).to be(true)
|
|
end
|
|
|
|
it_behaves_like 'records an onboarding progress action', :git_write do
|
|
let(:namespace) { project.namespace }
|
|
end
|
|
end
|
|
|
|
context 'with Project' do
|
|
it_behaves_like 'post_receive_service actions'
|
|
|
|
it 'returns link to create new merge request' do
|
|
message = <<~MESSAGE.strip
|
|
To create a merge request for #{branch_name}, visit:
|
|
http://#{Gitlab.config.gitlab.host}/#{project.full_path}/-/merge_requests/new?merge_request%5Bsource_branch%5D=#{branch_name}
|
|
MESSAGE
|
|
|
|
expect(subject).to include(build_basic_message(message))
|
|
end
|
|
|
|
it 'returns the link to an existing merge request when it exists' do
|
|
merge_request = create(:merge_request, source_project: project, source_branch: branch_name, target_branch: 'master')
|
|
message = <<~MESSAGE.strip
|
|
View merge request for feature:
|
|
#{project_merge_request_url(project, merge_request)}
|
|
MESSAGE
|
|
|
|
expect(subject).to include(build_basic_message(message))
|
|
end
|
|
|
|
context 'when printing_merge_request_link_enabled is false' do
|
|
let(:project) { create(:project, printing_merge_request_link_enabled: false) }
|
|
|
|
it 'returns no merge request messages' do
|
|
expect(subject).to be_blank
|
|
end
|
|
end
|
|
|
|
it 'does not invoke MergeRequests::PushOptionsHandlerService' do
|
|
expect(MergeRequests::PushOptionsHandlerService).not_to receive(:new)
|
|
|
|
subject
|
|
end
|
|
|
|
context 'when there are merge_request push options' do
|
|
let(:params) { super().merge(push_options: ['merge_request.create']) }
|
|
|
|
before do
|
|
project.add_developer(user)
|
|
end
|
|
|
|
it 'invokes MergeRequests::PushOptionsHandlerService' do
|
|
expect(MergeRequests::PushOptionsHandlerService).to receive(:new).and_call_original
|
|
|
|
subject
|
|
end
|
|
|
|
it 'creates a new merge request' do
|
|
expect { Sidekiq::Testing.fake! { subject } }.to change(MergeRequest, :count).by(1)
|
|
end
|
|
|
|
it 'links to the newly created merge request' do
|
|
message = <<~MESSAGE.strip
|
|
View merge request for #{branch_name}:
|
|
http://#{Gitlab.config.gitlab.host}/#{project.full_path}/-/merge_requests/1
|
|
MESSAGE
|
|
|
|
expect(subject).to include(build_basic_message(message))
|
|
end
|
|
|
|
it 'adds errors on the service instance to warnings' do
|
|
expect_any_instance_of(
|
|
MergeRequests::PushOptionsHandlerService
|
|
).to receive(:errors).at_least(:once).and_return(['my error'])
|
|
|
|
message = "WARNINGS:\nError encountered with push options 'merge_request.create': my error"
|
|
|
|
expect(subject).to include(build_alert_message(message))
|
|
end
|
|
|
|
it 'adds ActiveRecord errors on invalid MergeRequest records to warnings' do
|
|
invalid_merge_request = MergeRequest.new
|
|
invalid_merge_request.errors.add(:base, 'my error')
|
|
message = "WARNINGS:\nError encountered with push options 'merge_request.create': my error"
|
|
|
|
expect_any_instance_of(
|
|
MergeRequests::CreateService
|
|
).to receive(:execute).and_return(invalid_merge_request)
|
|
|
|
expect(subject).to include(build_alert_message(message))
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'with PersonalSnippet' do
|
|
let(:gl_repository) { "snippet-#{personal_snippet.id}" }
|
|
let(:repository) { personal_snippet.repository }
|
|
|
|
it_behaves_like 'post_receive_service actions'
|
|
|
|
it 'does not return link to create new merge request' do
|
|
expect(subject).to be_empty
|
|
end
|
|
|
|
it 'does not return the link to an existing merge request when it exists' do
|
|
create(:merge_request, source_project: project, source_branch: branch_name, target_branch: 'master')
|
|
|
|
expect(subject).to be_empty
|
|
end
|
|
end
|
|
|
|
context 'with ProjectSnippet' do
|
|
let(:gl_repository) { "snippet-#{project_snippet.id}" }
|
|
let(:repository) { project_snippet.repository }
|
|
|
|
it_behaves_like 'post_receive_service actions'
|
|
|
|
it 'does not return link to create new merge request' do
|
|
expect(subject).to be_empty
|
|
end
|
|
|
|
it 'does not return the link to an existing merge request when it exists' do
|
|
create(:merge_request, source_project: project, source_branch: branch_name, target_branch: 'master')
|
|
|
|
expect(subject).to be_empty
|
|
end
|
|
end
|
|
|
|
context 'broadcast message banner exists' do
|
|
it 'outputs a broadcast message' do
|
|
broadcast_message = create(:broadcast_message)
|
|
|
|
expect(subject).to include(build_alert_message(broadcast_message.message))
|
|
end
|
|
end
|
|
|
|
context 'broadcast message notification exists' do
|
|
it 'does not output a broadcast message' do
|
|
create(:broadcast_message, :notification)
|
|
|
|
expect(has_alert_messages?(subject)).to be_falsey
|
|
end
|
|
end
|
|
|
|
context 'broadcast message does not exist' do
|
|
it 'does not output a broadcast message' do
|
|
expect(has_alert_messages?(subject)).to be_falsey
|
|
end
|
|
end
|
|
|
|
context 'nil broadcast message' do
|
|
it 'does not output a broadcast message' do
|
|
allow(BroadcastMessage).to receive(:current).and_return(nil)
|
|
|
|
expect(has_alert_messages?(subject)).to be_falsey
|
|
end
|
|
end
|
|
|
|
context "broadcast message has a target_path" do
|
|
let!(:older_scoped_message) do
|
|
create(:broadcast_message, message: "Old top secret", target_path: "/company/sekrit-project")
|
|
end
|
|
|
|
let!(:latest_scoped_message) do
|
|
create(:broadcast_message, message: "Top secret", target_path: "/company/sekrit-project")
|
|
end
|
|
|
|
let!(:unscoped_message) do
|
|
create(:broadcast_message, message: "Hi")
|
|
end
|
|
|
|
context "no project path matches" do
|
|
it "does not output the scoped broadcast messages" do
|
|
expect(subject).not_to include(build_alert_message(older_scoped_message.message))
|
|
expect(subject).not_to include(build_alert_message(latest_scoped_message.message))
|
|
end
|
|
|
|
it "does output another message that doesn't have a target_path" do
|
|
expect(subject).to include(build_alert_message(unscoped_message.message))
|
|
end
|
|
end
|
|
|
|
context "project path matches" do
|
|
before do
|
|
allow(project).to receive(:full_path).and_return("/company/sekrit-project")
|
|
end
|
|
|
|
it "does output the latest scoped broadcast message" do
|
|
expect(subject).to include(build_alert_message(latest_scoped_message.message))
|
|
end
|
|
|
|
it "does not output the older scoped broadcast message" do
|
|
expect(subject).not_to include(build_alert_message(older_scoped_message.message))
|
|
end
|
|
|
|
it "does not output another message that doesn't have a target_path" do
|
|
expect(subject).not_to include(build_alert_message(unscoped_message.message))
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'with a redirected data' do
|
|
it 'returns redirected message on the response' do
|
|
project_moved = Gitlab::Checks::ProjectMoved.new(project.repository, user, 'http', 'foo/baz')
|
|
project_moved.add_message
|
|
|
|
expect(subject).to include(build_basic_message(project_moved.message))
|
|
end
|
|
end
|
|
|
|
context 'with new project data' do
|
|
it 'returns new project message on the response' do
|
|
project_created = Gitlab::Checks::ProjectCreated.new(project.repository, user, 'http')
|
|
project_created.add_message
|
|
|
|
expect(subject).to include(build_basic_message(project_created.message))
|
|
end
|
|
end
|
|
|
|
describe '#process_mr_push_options' do
|
|
context 'when repository belongs to a snippet' do
|
|
context 'with PersonalSnippet' do
|
|
let(:repository) { personal_snippet.repository }
|
|
|
|
it 'returns an error message' do
|
|
result = service.process_mr_push_options(push_options, changes)
|
|
|
|
expect(result).to match('Push options are only supported for projects')
|
|
end
|
|
end
|
|
|
|
context 'with ProjectSnippet' do
|
|
let(:repository) { project_snippet.repository }
|
|
|
|
it 'returns an error message' do
|
|
result = service.process_mr_push_options(push_options, changes)
|
|
|
|
expect(result).to match('Push options are only supported for projects')
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#merge_request_urls' do
|
|
context 'when repository belongs to a snippet' do
|
|
context 'with PersonalSnippet' do
|
|
let(:repository) { personal_snippet.repository }
|
|
|
|
it 'returns an empty array' do
|
|
expect(service.merge_request_urls).to be_empty
|
|
end
|
|
end
|
|
|
|
context 'with ProjectSnippet' do
|
|
let(:repository) { project_snippet.repository }
|
|
|
|
it 'returns an empty array' do
|
|
expect(service.merge_request_urls).to be_empty
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def build_alert_message(message)
|
|
{ 'type' => 'alert', 'message' => message }
|
|
end
|
|
|
|
def build_basic_message(message)
|
|
{ 'type' => 'basic', 'message' => message }
|
|
end
|
|
|
|
def has_alert_messages?(messages)
|
|
messages.any? do |message|
|
|
message['type'] == 'alert'
|
|
end
|
|
end
|
|
end
|