Files
gitlab-ce/spec/scripts/pipeline/pre_merge_checks_spec.rb
2025-07-10 21:07:54 +00:00

334 lines
12 KiB
Ruby

# frozen_string_literal: true
require 'fast_spec_helper'
require_relative '../../support/webmock'
require_relative '../../../scripts/pipeline/pre_merge_checks'
RSpec.describe PreMergeChecks, time_travel_to: Time.parse('2024-05-29T10:00:00 UTC'), feature_category: :tooling do
include StubENV
let(:instance) { described_class.new }
let(:project_id) { '42' }
let(:merge_request_iid) { '1' }
let(:target_branch) { 'master' }
let(:mr_pipelines_url) { "https://gitlab.test/api/v4/projects/#{project_id}/merge_requests/#{merge_request_iid}/pipelines" }
let(:latest_mr_pipeline_ref) { "refs/merge-requests/1/merge" }
let(:latest_mr_pipeline_status) { "success" }
let(:latest_mr_pipeline_created_at) { "2024-05-29T07:15:00 UTC" }
let(:latest_mr_pipeline_project_id) { project_id.to_i } # We get an integer from the API
let(:latest_mr_pipeline_web_url) { "https://gitlab.com/gitlab-org/gitlab/-/pipelines/1310472835" }
let(:latest_mr_pipeline_name) { "Ruby 3.2 MR [tier:3, gdk]" }
let(:latest_mr_pipeline_short) do
{
id: 1309901620,
ref: latest_mr_pipeline_ref,
status: latest_mr_pipeline_status,
project_id: latest_mr_pipeline_project_id,
source: "merge_request_event",
created_at: latest_mr_pipeline_created_at,
web_url: latest_mr_pipeline_web_url
}
end
let(:latest_mr_pipeline_detailed) do
latest_mr_pipeline_short.merge(name: latest_mr_pipeline_name)
end
let(:mr_pipelines) do
[
{
id: 1309903340,
ref: "refs/merge-requests/1/train",
status: "success",
project_id: project_id,
source: "merge_request_event",
created_at: "2024-05-29T07:30:00 UTC"
},
{
id: 1309903341,
ref: "refs/merge-requests/1/train",
status: "success",
project_id: project_id,
source: "merge_request_event",
created_at: "2024-05-29T07:15:00 UTC"
},
latest_mr_pipeline_short,
{
id: 1309753047,
ref: "refs/merge-requests/1/train",
status: "failed",
project_id: project_id,
source: "merge_request_event",
created_at: "2024-05-29T06:30:00 UTC"
},
{
id: 1308929843,
ref: "refs/merge-requests/1/train",
status: "success",
project_id: project_id,
source: "merge_request_event",
created_at: "2024-05-29T05:30:00 UTC"
},
{
id: 1308699353,
ref: "refs/merge-requests/1/train",
status: "failed",
project_id: project_id,
source: "merge_request_event",
created_at: "2024-05-29T04:30:00 UTC"
}
]
end
before do
stub_env(
'CI_API_V4_URL' => 'https://gitlab.test/api/v4',
'CI_PROJECT_ID' => project_id,
'CI_MERGE_REQUEST_IID' => merge_request_iid,
'CI_MERGE_REQUEST_TARGET_BRANCH_NAME' => target_branch
)
end
describe '#initialize' do
context 'when project_id is missing' do
let(:project_id) { nil }
it 'returns a failed PreMergeChecksStatus' do
expect(instance.execute).to be_a(described_class::PreMergeChecksStatus)
expect(instance.execute).not_to be_success
expect(instance.execute.message).to include("Missing project_id")
end
end
context 'when merge_request_iid is missing' do
let(:merge_request_iid) { nil }
it 'returns a failed PreMergeChecksStatus' do
expect(instance.execute).to be_a(described_class::PreMergeChecksStatus)
expect(instance.execute).not_to be_success
expect(instance.execute.message).to include("Missing merge_request_iid")
end
end
context 'when target_branch is missing' do
let(:target_branch) { nil }
it 'returns a failed PreMergeChecksStatus' do
expect(instance.execute).to be_a(described_class::PreMergeChecksStatus)
expect(instance.execute).not_to be_success
expect(instance.execute.message).to include("Missing target_branch")
end
end
end
describe '#execute' do
# rubocop:disable RSpec/VerifiedDoubles -- See the disclaimer above
let(:api_client) { double('Gitlab::Client') }
let(:latest_mr_pipeline) { double('pipeline', **latest_mr_pipeline_detailed) }
# We need to take some precautions when using the `gitlab` gem in this project.
#
# See https://docs.gitlab.com/ee/development/pipelines/internals.html#using-the-gitlab-ruby-gem-in-the-canonical-project.
#
before do
stub_request(:get, mr_pipelines_url).to_return(status: 200, body: mr_pipelines.to_json)
allow(instance).to receive(:api_client).and_return(api_client)
allow(api_client).to yield_pipelines(:merge_request_pipelines, mr_pipelines)
# Ensure we don't output to stdout while running tests
allow(instance).to receive(:puts)
end
def yield_pipelines(api_method, pipelines)
messages = receive_message_chain(api_method, :auto_paginate)
pipelines.inject(messages) do |stub, pipeline|
stub.and_yield(double(**pipeline))
end
end
# rubocop:enable RSpec/VerifiedDoubles
context 'when default arguments are present' do
context 'when we have a latest pipeline' do
before do
allow(api_client).to receive(:pipeline).with(project_id, mr_pipelines[2][:id]).and_return(latest_mr_pipeline)
end
context 'and the target branch is a stable branch' do
let(:target_branch) { 'a-stable-branch-stable-ee' }
context 'and the latest pipeline is not fresh enough' do
let(:latest_mr_pipeline_created_at) { "2024-05-26T09:30:00 UTC" }
it 'returns a failed PreMergeChecksStatus' do
expect(instance.execute).to be_a(described_class::PreMergeChecksStatus)
expect(instance.execute).not_to be_success
expect(instance.execute.message)
.to include(
"Expected latest pipeline (#{latest_mr_pipeline_web_url}) to be created within the last 72 hours " \
"(it was created 72.5 hours ago)!"
)
end
end
context 'and the latest pipeline is fresh enough' do
let(:latest_mr_pipeline_created_at) { "2024-05-26T10:30:00 UTC" }
it 'returns a successful PreMergeChecksStatus' do
expect(instance.execute).to be_a(described_class::PreMergeChecksStatus)
expect(instance.execute).to be_success
end
end
end
context 'and it passes all the checks' do
it 'returns a successful PreMergeChecksStatus' do
expect(instance.execute).to be_a(described_class::PreMergeChecksStatus)
expect(instance.execute).to be_success
end
end
context 'and it is not a merged results pipeline' do
let(:latest_mr_pipeline_ref) { "refs/merge-requests/1/head" }
it 'returns a failed PreMergeChecksStatus' do
expect(instance.execute).to be_a(described_class::PreMergeChecksStatus)
expect(instance.execute).not_to be_success
expect(instance.execute.message)
.to include("Expected to have a Merged Results pipeline but got #{latest_mr_pipeline_ref}")
end
end
shared_examples 'non-success pipeline response' do
it 'returns a failed PreMergeChecksStatus' do
expect(instance.execute).to be_a(described_class::PreMergeChecksStatus)
expect(instance.execute).not_to be_success
expect(instance.execute.message).to include(
"Expected latest pipeline (#{latest_mr_pipeline_web_url}) to be successful!"
)
expect(instance.execute.message).to include("Pipeline status was \"#{latest_mr_pipeline_status}\".")
expect(instance.execute.message).to include("Please start a new pipeline.")
end
end
context 'and it is running' do
it_behaves_like 'non-success pipeline response' do
let(:latest_mr_pipeline_status) { "running" }
end
end
context 'and it is failed' do
it_behaves_like 'non-success pipeline response' do
let(:latest_mr_pipeline_status) { "failed" }
end
end
context 'and it is canceled' do
it_behaves_like 'non-success pipeline response' do
let(:latest_mr_pipeline_status) { "canceled" }
end
end
context 'and it is not fresh enough' do
let(:latest_mr_pipeline_created_at) { "2024-05-29T01:30:00 UTC" }
it 'returns a failed PreMergeChecksStatus' do
expect(instance.execute).to be_a(described_class::PreMergeChecksStatus)
expect(instance.execute).not_to be_success
expect(instance.execute.message)
.to include(
"Expected latest pipeline (#{latest_mr_pipeline_web_url}) to be created within the last 8 hours " \
"(it was created 8.5 hours ago)!"
)
end
end
context 'and it is a predictive pipeline' do
let(:latest_mr_pipeline_name) { "Ruby 3.2 MR [predictive]" }
it 'returns a failed PreMergeChecksStatus' do
expect(instance.execute).to be_a(described_class::PreMergeChecksStatus)
expect(instance.execute).not_to be_success
expect(instance.execute.message).to include(
"Expected latest pipeline (#{latest_mr_pipeline_web_url}) not to be a predictive pipeline!"
)
expect(instance.execute.message).to include("Pipeline name was \"#{latest_mr_pipeline_name}\".")
end
context 'when also matching the required tier identifier' do
let(:latest_mr_pipeline_name) { "Ruby 3.2 MR [predictive,tier:3]" }
it 'returns a failed PreMergeChecksStatus' do
expect(instance.execute).to be_a(described_class::PreMergeChecksStatus)
expect(instance.execute).not_to be_success
expect(instance.execute.message).to include(
"Expected latest pipeline (#{latest_mr_pipeline_web_url}) not to be a predictive pipeline!"
)
expect(instance.execute.message).to include("Pipeline name was \"#{latest_mr_pipeline_name}\".")
end
end
end
context 'and it is not a tier-3 pipeline' do
let(:latest_mr_pipeline_name) { "Ruby 3.2 MR [tier:2]" }
it 'returns a failed PreMergeChecksStatus' do
expect(instance.execute).to be_a(described_class::PreMergeChecksStatus)
expect(instance.execute).not_to be_success
expect(instance.execute.message).to include(
"Expected latest pipeline (#{latest_mr_pipeline_web_url}) to be a tier-3 pipeline"
)
expect(instance.execute.message).to include("Pipeline name was \"#{latest_mr_pipeline_name}\".")
end
end
context 'and it is qa-only pipeline' do
let(:latest_mr_pipeline_name) { "Ruby 3.2 MR [types:qa,qa-gdk]" }
it 'returns a successful PreMergeChecksStatus' do
expect(instance.execute).to be_a(described_class::PreMergeChecksStatus)
expect(instance.execute).to be_success
end
end
context 'and was run in a different project' do
let(:latest_mr_pipeline_project_id) { 5678 }
it 'returns a failed PreMergeChecksStatus' do
expect(instance.execute).to be_a(described_class::PreMergeChecksStatus)
expect(instance.execute).not_to be_success
expect(instance.execute.message).to include(
"Expected to have a latest pipeline that ran in project ##{project_id} but got none!"
)
end
end
end
context 'when we do not have a latest pipeline' do
let(:mr_pipelines) do
[
{
id: 1309903341,
ref: "refs/merge-requests/1/train",
status: "success",
source: "merge_request_event",
created_at: "2024-05-29T08:29:43.472Z"
}
]
end
it 'returns a failed PreMergeChecksStatus' do
expect(instance.execute).to be_a(described_class::PreMergeChecksStatus)
expect(instance.execute).not_to be_success
expect(instance.execute.message).to include(
"Expected to have a latest pipeline that ran in project ##{project_id} but got none!"
)
end
end
end
end
end