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