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