spec/lib/release_tools/security/merge_requests_merger_spec.rb (245 lines of code) (raw):
# frozen_string_literal: true
require 'spec_helper'
describe ReleaseTools::Security::MergeRequestsMerger do
let(:client) { spy(:client) }
let(:batch_merger) { described_class.new }
let(:issue_crawler) { instance_double(ReleaseTools::Security::IssueCrawler) }
let(:mwps_service) { double(:mwps_service) }
let(:author) { create(:user, id: 123, username: 'joe') }
let(:notifier) { instance_double(ReleaseTools::Slack::ReleaseJobEndNotifier, send_notification: true) }
let(:default_branch) { 'master' }
let(:mr1) do
create(
:merge_request,
state: 'opened',
iid: 1,
project_id: 1,
source_branch: 'security-fixes-test-vulnerability',
target_branch: default_branch,
web_url: 'https://test.com/security/-/merge_requests/1',
author: author
)
end
let(:mr2) do
create(
:merge_request,
state: 'opened',
iid: 2,
project_id: 1,
target_branch: '12-10-stable-ee',
web_url: 'https://test.com/security/-/merge_requests/2',
author: author
)
end
let(:mr3) do
create(
:merge_request,
state: 'opened',
iid: 3,
project_id: 1,
target_branch: '12-9-stable-ee',
web_url: 'https://test.com/security/-/merge_requests/3',
author: author
)
end
let(:mr4) do
create(
:merge_request,
state: 'opened',
iid: 4,
project_id: 1,
target_branch: '12-8-stable-ee',
web_url: 'https://test.com/security/-/merge_requests/4',
author: author
)
end
let(:merge_requests) { [mr1, mr2, mr3, mr4] }
let(:issue1) do
create(
:issue,
iid: 1,
project_id: 1,
web_url: 'https://test.com/security/-/issues/1',
merge_requests: merge_requests,
merge_request_targeting_default_branch: mr1,
merge_requests_targeting_default_branch: [mr1],
backports: [mr2, mr3, mr4],
allowed_to_early_merge?: true
)
end
let(:issues) { [issue1] }
before do
stub_const('ReleaseTools::Security::Client', client)
allow(ReleaseTools::Security::IssueCrawler)
.to receive(:new)
.and_return(issue_crawler)
allow(ReleaseTools::Security::IssueTable::Service)
.to receive(:new)
.and_return(double(execute: nil))
allow(ReleaseTools::Slack::ReleaseJobEndNotifier)
.to receive(:new)
.and_return(notifier)
end
describe '#execute' do
before do
allow(issue_crawler)
.to receive(:upcoming_security_issues_and_merge_requests)
.and_return(issues)
end
context 'when processing MRs targeting default branch' do
let(:batch_merger) do
described_class.new(merge_default: true)
end
before do
allow(ReleaseTools::Security::MergeWhenPipelineSucceedsService)
.to receive(:new)
.with(client, mr1)
.and_return(mwps_service)
end
it 'only process MR targeting default branch' do
expect(mwps_service).to receive(:execute)
expect(ReleaseTools::Slack::ReleaseJobEndNotifier).to receive(:new)
.with(job_type: 'Default merge', status: :success, release_type: :patch)
.and_return(notifier)
without_dry_run do
batch_merger.execute
end
end
context 'without an MR targeting default branch' do
let(:issue1) do
create(
:issue,
iid: 1,
web_url: 'https://test.com/security/-/issues/1',
merge_requests: merge_requests,
merge_request_targeting_default_branch: nil,
backports: [mr2, mr3, mr4],
allowed_to_early_merge?: false
)
end
it 'does nothing' do
expect(mwps_service).not_to receive(:execute)
without_dry_run do
batch_merger.execute
end
end
end
context 'when a security issue is not allowed to early merge' do
let(:issue1) do
create(
:issue,
iid: 1,
web_url: 'https://test.com/security/-/issues/1',
merge_requests: merge_requests,
merge_request_targeting_default_branch: mr1,
backports: [mr2, mr3, mr4],
allowed_to_early_merge?: false
)
end
it 'does not process MR targeting default branch' do
expect(mwps_service).not_to receive(:execute)
without_dry_run do
batch_merger.execute
end
end
end
context 'when there is an error' do
it 'sends a slack failure notification and raises' do
expect(mwps_service).to receive(:execute).and_raise(StandardError)
expect(ReleaseTools::Slack::ReleaseJobEndNotifier).to receive(:new)
.with(job_type: 'Default merge', status: :failed, release_type: :patch)
.and_return(notifier)
expect { without_dry_run { batch_merger.execute } }.to raise_error(StandardError)
end
end
end
context 'when pending merge requests' do
let(:issue1) do
create(
:issue,
iid: 1,
web_url: 'https://test.com/security/-/issues/1',
merge_requests: merge_requests,
merge_request_targeting_default_branch: mr1,
backports: [mr2, mr3, mr4],
allowed_to_early_merge?: true,
pending_merge_requests: [mr2, mr3, mr4],
default_merge_request_handled?: true
)
end
let(:merge_request) do
create(
:merge_request,
target_branch: default_branch,
merge_commit_sha: 'asdf',
project_id: 1,
web_url: 'https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/1'
)
end
before do
issue1.backports.each do |mr|
allow(client)
.to receive(:accept_merge_request)
.with(mr.project_id, mr.iid, squash: true, should_remove_source_branch: true)
.and_return(merge_request)
end
end
it 'processes pending merge requests' do
issue1.backports.each do |backport|
expect(client)
.to receive(:accept_merge_request)
.with(backport.project_id, backport.iid, squash: true, should_remove_source_branch: true)
end
expect(ReleaseTools::Slack::ReleaseJobEndNotifier).to receive(:new)
.with(job_type: 'Merge', status: :success, release_type: :patch)
.and_return(notifier)
without_dry_run do
batch_merger.execute
end
end
it 'updates the patch release status metric with closed status' do
expect(ReleaseTools::Metrics::PatchReleaseStatus).to receive(:new).with(status: :closed)
.and_return(instance_double(ReleaseTools::Metrics::PatchReleaseStatus, execute: true))
without_dry_run do
batch_merger.execute
end
end
context 'when the default MR is not merged' do
before do
allow(issue1).to receive(:default_merge_request_handled?).and_return(false)
end
it 'skips the issue when the default MR is not merged' do
expect(client).not_to receive(:accept_merge_request)
expect(batch_merger.logger).to receive(:fatal).with(
'Issue skipped. Default merge request has not been merged',
{ issue: issue1.web_url }
)
expect(ReleaseTools::Slack::ReleaseJobEndNotifier).to receive(:new)
.with(job_type: 'Merge', status: :failed, release_type: :patch)
.and_return(notifier)
without_dry_run do
batch_merger.execute
end
end
end
context 'when there is an error' do
it 'sends a slack failure notification and raises' do
expect(client).to receive(:accept_merge_request).and_raise(StandardError)
expect(ReleaseTools::Slack::ReleaseJobEndNotifier).to receive(:new)
.with(job_type: 'Merge', status: :failed, release_type: :patch)
.and_return(notifier)
expect { without_dry_run { batch_merger.execute } }.to raise_error(StandardError)
end
end
end
context 'when security implementation issues are not ready' do
it 'does not process the issues' do
allow(issue_crawler)
.to receive(:upcoming_security_issues_and_merge_requests)
.and_return([])
expect(batch_merger)
.not_to receive(:process_merge_requests)
batch_merger.execute
end
end
end
end