spec/lib/release_tools/security/issue_table/builder_spec.rb (233 lines of code) (raw):

# frozen_string_literal: true require 'spec_helper' describe ReleaseTools::Security::IssueTable::Builder do subject(:generate) do described_class.new(release_manager_comments: '', issues: issues).generate end let(:issue_and_severity_column_index) { 0 } let(:security_target_column_index) { 1 } let(:master_mr_merged_column_index) { 2 } let(:master_mr_deployed_column_index) { 3 } let(:backports_merged_column_index) { 4 } let(:bot_comments_column_index) { 5 } let(:release_manager_comments_column_index) { 6 } let(:mr1) { build(:merge_request, project_id: 1) } let(:issue1_mrs) { [mr1] } let(:release_issue) { build(:issue, project_id: 1) } let(:issue1) { build(:issue, project_id: 1) } let(:issues) { [build(:security_implementation_issue, issue: issue1, merge_requests: issue1_mrs)] } let(:client) { instance_spy(ReleaseTools::Security::Client) } let(:validator) { instance_double(ReleaseTools::Security::MergeRequestsValidator) } before do allow(ReleaseTools::GitlabClient).to receive_messages(issue_notes: double(auto_paginate: []), merge_requests: []) end shared_examples 'adding managed versioning comment' do context 'when the project is manually released' do let(:issue1) { build(:issue, project_id: ReleaseTools::ManagedVersioning::PROJECTS_NEEDING_MANUAL_RELEASES.first.security_id) } it 'adds a note about managed versioning' do result = generate row = find_row(result, issue1.web_url) expect(row[bot_comments_column_index]).to include( 'Managed versioning. May require manual merging.' ) end end end describe '#generate' do before do allow(ReleaseTools::Security::MergeRequestsValidator) .to receive(:new) .and_return(validator) allow(validator) .to receive(:execute) .with({ merge_requests: [mr1] }) .and_return([[mr1], []]) end it 'adds issue URL to first column' do result = generate row = find_row(result, issue1.web_url) expect(row[issue_and_severity_column_index]).to match(/^ +#{issue1.web_url} +$/) end context 'with severity label' do let(:issue1) { build(:issue, project_id: 1, labels: ['severity::2']) } it 'adds issue URL and severity label to first column' do result = generate row = find_row(result, issue1.web_url) expect(row[issue_and_severity_column_index]).to match(/^ +#{issue1.web_url} - ~severity::2 +$/) end end context 'when default branch MR has been merged' do let(:issue2) do build(:issue, project_id: 1) end let(:mr2) do build( :merge_request, :merged, project_id: 1 ) end let(:issues) do [ build(:security_implementation_issue, issue: issue1, merge_requests: [mr1]), build(:security_implementation_issue, issue: issue2, merge_requests: [mr2]) ] end it 'adds a checkmark to master MR merged column' do result = generate row = find_row(result, issue2.web_url) expect(row[master_mr_merged_column_index]).to include(':white_check_mark:') end it 'keeps issues where master MR has been merged at top of table' do result = generate rows = table_rows(result) expect(rows.length).to eq(2) expect(rows[0]).to include(issue2.web_url) expect(rows[1]).to include(issue1.web_url) end end context 'when backports have been merged' do let(:backport_mr) { build(:merge_request, :merged, target_branch: '15-10-stable') } let(:issue1_mrs) { [mr1, backport_mr] } it 'adds a checkmark to backports merged column' do result = generate row = find_row(result, issue1.web_url) expect(row[backports_merged_column_index]).to include(':white_check_mark:') end end context 'when default MR has been deployed' do before do allow(ReleaseTools::GitlabClient) .to receive(:merge_requests) .with(mr1.project_id, { iids: [mr1.iid], environment: 'gprd' }) .and_return([mr1]) end it 'adds a checkmark to master MR deployed column' do result = generate row = find_row(result, issue1.web_url) expect(row[master_mr_deployed_column_index]).to include(':white_check_mark:') end end context 'when there are pending_reasons' do it 'adds a comment to the bot comments column' do result = generate row = find_row(result, issue1.web_url) expect(row[bot_comments_column_index]).to include( 'Every security issue is expected to have 4 MRs; 3 backports targeting the versions in the [next scheduled patch release](https://gitlab.com/gitlab-org/gitlab/-/issues/?label_name%5B%5D=upcoming%20security%20release) and 1 MR targeting master.' ) end context 'when issue has already been processed' do let(:mr1) { build(:merge_request, :merged, project_id: 1) } it 'does not add comment to bot comments column' do result = generate row = find_row(result, issue1.web_url) expect(row[bot_comments_column_index]).to match(/^ +$/) end end it_behaves_like 'adding managed versioning comment' end context 'with security-target label' do before do allow(issue1) .to receive(:labels) .and_return(['security-target']) end it 'adds a checkmark to the security-target column' do result = generate row = find_row(result, issue1.web_url) expect(row[security_target_column_index]).to include(':white_check_mark:') end end context 'when MWPS has been set on default MR' do let(:release_bot_user) { build(:user, id: ReleaseTools::Security::ImplementationIssue::GITLAB_RELEASE_BOT_ID) } let(:mr1) do build(:merge_request, project_id: 1, merge_when_pipeline_succeeds: true, assignees: [release_bot_user]) end let(:mr2) do build(:merge_request, project_id: 1, target_branch: '15-10-stable', assignees: [release_bot_user]) end let(:mr3) do build(:merge_request, project_id: 1, target_branch: '15-9-stable', assignees: [release_bot_user]) end let(:mr4) do build(:merge_request, project_id: 1, target_branch: '15-8-stable', assignees: [release_bot_user]) end let(:issue1_mrs) { [mr1, mr2, mr3, mr4] } before do allow(validator) .to receive(:execute) .with({ merge_requests: issue1_mrs }) .and_return([issue1_mrs, []]) end it 'adds a comment to bot comment column' do result = generate row = find_row(result, issue1.web_url) expect(row[bot_comments_column_index]) .to include("MWPS set on default branch MR: https://example.com/foo/bar/-/merge_requests/#{mr1.iid}") end it_behaves_like 'adding managed versioning comment' end context 'when release manager comments are passed as parameter' do subject(:generate) do described_class.new(release_manager_comments: release_manager_comments, issues: issues).generate end let(:release_manager_comments) do { issue1.web_url => 'Some comment', issue2.web_url => 'Some other comment' } end let(:mr2) do build( :merge_request, :merged, project_id: 1 ) end let(:issue2) { build(:issue, project_id: 1, labels: ['severity::2']) } let(:issues) do [ build(:security_implementation_issue, issue: issue1, merge_requests: [mr1]), build(:security_implementation_issue, issue: issue2, merge_requests: [mr2]) ] end it 'retains release manager comments in table' do result = generate row = find_row(result, issue1.web_url) expect(row[release_manager_comments_column_index]).to include('Some comment') row = find_row(result, issue2.web_url) expect(row[release_manager_comments_column_index]).to include('Some other comment') end it 'generates expected table' do expected_table = <<~EOF ## Security issues | Issue | ~"security-target" | Master merged? | Deployed? | Backports merged? | Bot Comments | Release manager comments | |-------|--------------------|----------------|-----------|-------------------|--------------|--------------------------| | #{issue2.web_url} - ~severity::2 | | :white_check_mark: | | | | Some other comment | | #{issue1.web_url} | | | | | - Every security issue is expected to have 4 MRs; 3 backports targeting the versions in the [next scheduled patch release](https://gitlab.com/gitlab-org/gitlab/-/issues/?label_name%5B%5D=upcoming%20security%20release) and 1 MR targeting master. If this issue legitimately does not need to have all 4 MRs, please add the ~"reduced backports" label to this issue<br/>- The following merge requests need to be assigned to the `@gitlab-release-tools-bot`: #{mr1.web_url} | Some comment | --- :robot: <sub>This table was generated by [release-tools](https://gitlab.com/gitlab-org/release-tools/). Please open an issue in the [Delivery team issue tracker](https://gitlab.com/gitlab-com/gl-infra/delivery/-/issues) if you have any suggestions or bug reports.</sub> EOF expect(generate).to eq(expected_table) end end end # @param comment_str [String] is the full markdown comment string # @param str_to_find [String] is a string in the row that is to be returned. This can be the issue URL for example. # @return [Array<String>] the row that contains str_to_find, split by each column. So each element in the array # is a column of the row. def find_row(comment_str, str_to_find) rows = table_rows(comment_str) row = rows.detect { |r| r.include?(str_to_find) } return unless row row .delete_prefix('|') .delete_suffix('|') .split('|') end # @param comment_str [String] is the full markdown comment string # @return [Array<String>] the rows of the table excluding the header rows. Each element in the array # is a row in the table. def table_rows(comment_str) rows = comment_str .delete_prefix(ReleaseTools::Security::IssueTable::Service::NOTE_HEADER) .strip .split("\n") .select { |r| r.include?('|') } # Exclude the header rows rows[2..] end end