spec/lib/release_tools/passing_build_spec.rb (405 lines of code) (raw):
# frozen_string_literal: true
require 'spec_helper'
describe ReleaseTools::PassingBuild do
let(:fake_commit) { double('Commit', id: SecureRandom.hex(20)) }
let(:target_branch) { '11-10-auto-deploy-1234' }
let(:project) { ReleaseTools::Project::GitlabEe }
let(:client) { ReleaseTools::GitlabClient }
subject(:service) { described_class.new(target_branch, project, client: client) }
before do
# Reduce our fixture payload
stub_const('ReleaseTools::Commits::MAX_COMMITS_TO_CHECK', 5)
end
shared_examples 'a passing build commit seeker' do
let(:commits) { instance_double(ReleaseTools::Commits) }
before do
allow(ReleaseTools::Commits).to receive(:new).and_return(commits)
end
it 'correctly identify the requested operation' do
expect(service).to receive(:passing_build_commit).with(operation)
subject
end
it 'raises an error without a dev commit' do
expect(commits).to receive(:latest_successful_on_build)
.with({ since_last_auto_deploy: true })
.and_return(nil)
expect { service.for_auto_deploy_branch }
.to raise_error(/Unable to find a passing/)
end
it 'returns the latest successful commit on Build' do
expect(commits)
.to receive(:latest_successful_on_build)
.with({ since_last_auto_deploy: true })
.and_return(fake_commit)
expect(service.for_auto_deploy_branch).to eq(fake_commit)
end
it 'returns the latest commit when auto_deploy_tag_latest is enabled' do
enable_feature(:auto_deploy_tag_latest)
expect(commits).to receive(:latest)
.and_return(fake_commit)
expect(service.for_auto_deploy_branch).to eq(fake_commit)
end
context 'when the project is not GitLab' do
let(:project) { ReleaseTools::Project::Gitaly }
%w[preparing pending running canceled created].each do |status_name|
it "rejects unsuccessfull commits - #{status_name}" do
commit = build(:commit, status: status_name)
expect(client).to receive(:commit).with(project.auto_deploy_path, ref: commit.id).and_return(commit)
expect(commits).to receive(:latest_successful_on_build)
.with({ since_last_auto_deploy: true })
.and_yield(commit.id)
# NOTE: we are stubbing Commits#latest_successful_on_build so this test
# can only verify the block provided by PassingBuild. This is why here
# we return a boolean
expect(subject).to be_falsey
end
end
it 'approves successfull commits' do
commit = build(:commit, status: 'success')
expect(client).to receive(:commit).with(project.auto_deploy_path, ref: commit.id).and_return(commit)
expect(commits).to receive(:latest_successful_on_build)
.with({ since_last_auto_deploy: true })
.and_yield(commit.id)
# NOTE: we are stubbing Commits#latest_successful_on_build so this test
# can only verify the block provided by PassingBuild. This is why here
# we return a boolean
expect(subject).to be_truthy
end
end
context 'when the project is GitLab' do
let(:project) { ReleaseTools::Project::GitlabEe }
it 'approves successful commits that have a compile-production-assets job' do
commit = build(:commit, status: 'success')
expect(client).to receive(:commit).with(project.auto_deploy_path, ref: commit.id).and_return(commit)
expect(commits).to receive(:latest_successful_on_build)
.with({ since_last_auto_deploy: true })
.and_yield(commit.id)
job1 = build(:job, name: 'test-job')
job2 = build(:job, name: 'compile-production-assets')
page = Gitlab::PaginatedResponse.new([job1, job2])
expect(client)
.to receive(:pipeline_jobs)
.with(project.auto_deploy_path, commit.last_pipeline.id)
.and_return(page)
.once
# NOTE: we are stubbing Commits#latest_successful_on_build so this test
# can only verify the block provided by PassingBuild. This is why here
# we return a boolean
expect(subject).to be_truthy
end
it 'approves successful commits that have a compile-production-assets and build-assets-image job' do
commit = build(:commit, status: 'success')
expect(client).to receive(:commit).with(project.auto_deploy_path, ref: commit.id).and_return(commit)
expect(commits).to receive(:latest_successful_on_build)
.with({ since_last_auto_deploy: true })
.and_yield(commit.id)
job2 = build(:job, name: 'compile-production-assets')
job1 = build(:job, name: 'build-assets-image')
page = Gitlab::PaginatedResponse.new([job1, job2])
expect(client)
.to receive(:pipeline_jobs)
.with(project.auto_deploy_path, commit.last_pipeline.id)
.and_return(page)
.once
# NOTE: we are stubbing Commits#latest_successful_on_build so this test
# can only verify the block provided by PassingBuild. This is why here
# we return a boolean
expect(subject).to be_truthy
end
it 'rejects successful commits that do not have a build-assets-image and compile-production-assets job' do
commit = build(:commit, status: 'success')
expect(client).to receive(:commit).with(project.auto_deploy_path, ref: commit.id).and_return(commit)
expect(commits).to receive(:latest_successful_on_build)
.with({ since_last_auto_deploy: true })
.and_yield(commit.id)
job1 = double(:job, name: 'compile-assets')
job2 = double(:job, name: 'test-job')
page = Gitlab::PaginatedResponse.new([job1, job2])
expect(client)
.to receive(:pipeline_jobs)
.with(project.auto_deploy_path, commit.last_pipeline.id)
.and_return(page)
.once
# NOTE: we are stubbing Commits#latest_successful_on_build so this test
# can only verify the block provided by PassingBuild. This is why here
# we return a boolean
expect(subject).to be_falsey
end
end
end
shared_examples 'gitlab delegates to default branch' do
let(:project) { ReleaseTools::Project::GitlabEe }
%w[preparing pending running canceled created failed].each do |status_name|
it "delegates the results to the default branch (success) - #{status_name}" do
commit = build(:commit, status: status_name)
expect(client).to receive(:commit).with(project.auto_deploy_path, ref: commit.id).and_return(commit)
expect(commits).to receive(:latest_successful_on_build)
.with({ since_last_auto_deploy: true })
.and_yield(commit.id)
pipelines = build_list(:pipeline, 2, :success)
expect(client).to receive(:pipelines)
.with(
project.auto_deploy_path,
{
sha: commit.id,
ref: project.default_branch,
status: 'success'
}
)
.and_return(Gitlab::PaginatedResponse.new(pipelines))
full_pipeline_jobs = Gitlab::PaginatedResponse.new(
[
double('test job', name: 'test'),
double('assets job', name: 'build-assets-image')
]
)
docs_pipeline_jobs = Gitlab::PaginatedResponse.new(
[
double('docs lint', name: 'lint docs')
]
)
expect(client).to receive(:pipeline_jobs)
.with(project.auto_deploy_path, pipelines.first.id)
.and_return(docs_pipeline_jobs)
expect(client).to receive(:pipeline_jobs)
.with(project.auto_deploy_path, pipelines.last.id)
.and_return(full_pipeline_jobs)
# NOTE: we are stubbing Commits#latest_successful_on_build so this test
# can only verify the block provided by PassingBuild. This is why here
# we return a boolean
expect(subject).to be_truthy
end
it "delegates the results to the default branch (failure) - #{status_name}" do
commit = build(:commit, status: status_name)
expect(client).to receive(:commit).with(project.auto_deploy_path, ref: commit.id).and_return(commit)
expect(commits).to receive(:latest_successful_on_build)
.with({ since_last_auto_deploy: true })
.and_yield(commit.id)
pipelines = build_list(:pipeline, 2, :success)
expect(client).to receive(:pipelines)
.with(
project.auto_deploy_path,
{
sha: commit.id,
ref: project.default_branch,
status: 'success'
}
)
.and_return(Gitlab::PaginatedResponse.new(pipelines))
docs_pipeline_jobs = Gitlab::PaginatedResponse.new(
[
double('docs lint', name: 'lint docs')
]
)
pipelines.each do |pipeline|
expect(client).to receive(:pipeline_jobs)
.with(project.auto_deploy_path, pipeline.id)
.and_return(docs_pipeline_jobs)
end
# NOTE: we are stubbing Commits#latest_successful_on_build so this test
# can only verify the block provided by PassingBuild. This is why here
# we return a boolean
expect(subject).to be_falsey
end
end
end
describe '#for_auto_deploy_branch' do
subject { service.for_auto_deploy_branch }
let(:operation) { :branch }
it_behaves_like 'a passing build commit seeker' do
it_behaves_like 'gitlab delegates to default branch'
end
end
describe '#for_auto_deploy_tag' do
subject { service.for_auto_deploy_tag }
let(:operation) { :tag }
it_behaves_like 'a passing build commit seeker' do
context 'when the project is GitLab' do
let(:project) { ReleaseTools::Project::GitlabEe }
%w[preparing skipped pending running canceled created].each do |status_name|
it "approves non-failed commits - #{status_name}" do
commit = build(:commit, status: status_name)
expect(client).to receive(:commit).with(project.auto_deploy_path, ref: commit.id).and_return(commit)
expect(commits).to receive(:latest_successful_on_build)
.with({ since_last_auto_deploy: true })
.and_yield(commit.id)
# NOTE: we are stubbing Commits#latest_successful_on_build so this test
# can only verify the block provided by PassingBuild. This is why here
# we return a boolean
expect(subject).to be_truthy
end
end
end
end
end
describe '#next_commit' do
let(:commits) { instance_double(ReleaseTools::Commits) }
before do
allow(ReleaseTools::Commits).to receive(:new).and_return(commits)
end
it 'calls commits.next_commit' do
expect(service).to receive(:passing_build_commit).and_return(double(id: 'commit1'))
expect(commits).to receive(:next_commit).with('commit1').and_return(double(id: 'commit2'))
expect(service.next_commit.id).to eq('commit2')
end
it 'raises error when passing_build_commit does not exist' do
expect(commits).to receive(:latest_successful_on_build)
.with({ since_last_auto_deploy: true })
.and_return(nil)
expect { service.next_commit }.to raise_error(/Unable to find a passing/)
end
end
describe '#previous_package_sha' do
let(:commits) { instance_double(ReleaseTools::Commits) }
before do
allow(ReleaseTools::Commits).to receive(:new).and_return(commits)
end
it 'delegates to commits.find_last_auto_deploy_limit_sha' do
expected_sha = 'abc123'
expect(commits)
.to receive(:find_last_auto_deploy_limit_sha)
.and_return(expected_sha)
expect(service.previous_package_sha).to eq(expected_sha)
end
it 'returns nil when no previous auto-deploy package exists' do
expect(commits)
.to receive(:find_last_auto_deploy_limit_sha)
.and_return(nil)
expect(service.previous_package_sha).to be_nil
end
end
describe '#latest_successful' do
let(:project) { ReleaseTools::Project::GitlabCe }
let(:target_branch) { 'master' }
it 'returns the latest successful commit' do
VCR.use_cassette('commits/list') do
commit = service.latest_successful
expect(commit.id).to eq 'a5f13e591f617931434d66263418a2f26abe3abe'
end
end
end
describe '#success_for_auto_deploy_rollout?' do
subject(:commits) { service }
let(:project) { ReleaseTools::Project::Gitaly }
let(:client) { double('ReleaseTools::GitlabClient') }
it 'returns true when status is success' do
commit = double('commit', id: 'abc', status: 'success')
expect(client)
.to receive(:commit)
.with(project.auto_deploy_path, ref: commit.id)
.and_return(commit)
.once
expect(client).not_to receive(:pipeline_jobs)
expect(service.success_for_auto_deploy_rollout?(commit.id)).to be true
end
it 'returns false when the build is skipped' do
commit = double('commit', id: 'abc', status: 'skipped')
expect(client)
.to receive(:commit)
.with(project.auto_deploy_path, ref: commit.id)
.and_return(commit)
.once
expect(client).not_to receive(:pipeline_jobs)
expect(service.success_for_auto_deploy_rollout?(commit.id)).to be false
end
it 'returns false when the build has not yet finished' do
%w[preparing pending running].each do |status_name|
commit = build(:commit, status: status_name)
allow(client).to receive(:commit).and_return(commit)
expect(service).not_to receive(:success_on_default_branch?)
expect(service.success_for_auto_deploy_rollout?(commit.id)).to be false
end
end
context 'when the project is GitLab' do
let(:project) { ReleaseTools::Project::GitlabEe }
it 'returns true when the build has not yet finished but passed on the default branch' do
%w[preparing pending running canceled created].each do |status_name|
commit = build(:commit, status: status_name)
allow(client).to receive(:commit).and_return(commit)
expect(commits)
.to receive(:success_on_default_branch?)
.with(commit.id)
.and_return(true)
expect(service.success_for_auto_deploy_rollout?(commit)).to be true
end
end
it 'also checks if the pipeline is a full pipeline when status is success' do
commit = build(:commit, status: 'success')
job1 = double(:job, name: 'compile-assets')
job2 = double(:job, name: 'build-assets-image')
page = Gitlab::PaginatedResponse.new([job1, job2])
expect(client)
.to receive(:commit)
.with(project.auto_deploy_path, ref: commit.id)
.and_return(commit)
.once
expect(client)
.to receive(:pipeline_jobs)
.with(project.auto_deploy_path, commit.last_pipeline.id)
.and_return(page)
.once
expect(service.success_for_auto_deploy_rollout?(commit.id)).to be true
end
it 'returns false when the pipeline is not a full pipeline' do
commit = build(:commit, status: 'success')
job1 = double(:job, name: 'compile-assets')
job2 = double(:job, name: 'docs lint')
page = Gitlab::PaginatedResponse.new([job1, job2])
expect(client)
.to receive(:commit)
.with(project.auto_deploy_path, ref: commit.id)
.and_return(commit)
.once
expect(client)
.to receive(:pipeline_jobs)
.with(project.auto_deploy_path, commit.last_pipeline.id)
.and_return(page)
.once
expect(service.success_for_auto_deploy_rollout?(commit.id)).to be false
end
it 'does check on default branch when pipeline failed' do
commit = build(:commit, status: 'skipped')
status = double('success_on_default_branch')
expect(client)
.to receive(:commit)
.with(project.auto_deploy_path, ref: commit.id)
.and_return(commit)
.once
expect(commits)
.to receive(:success_on_default_branch?)
.with(commit.id)
.and_return(status)
expect(service.success_for_auto_deploy_rollout?(commit.id)).to eq(status)
end
end
end
describe '#success_on_default_branch' do
let(:commit) { double(:commit) }
it 'returns true when a passing build is found' do
pipeline = double(:pipeline, status: 'success')
expect(client)
.to receive(:pipelines)
.with(
project.auto_deploy_path,
{
sha: commit,
ref: project.default_branch,
status: 'success'
}
)
.and_return(Gitlab::PaginatedResponse.new([pipeline]))
expect(service)
.to receive(:full_pipeline?)
.with(pipeline)
.and_return(true)
expect(service.send(:success_on_default_branch?, commit)).to be(true)
end
it 'returns false when a the pipeline is not a full pipeline' do
pipeline = double(:pipeline, status: 'success')
expect(client)
.to receive(:pipelines)
.with(
project.auto_deploy_path,
{
sha: commit,
ref: project.default_branch,
status: 'success'
}
)
.and_return(Gitlab::PaginatedResponse.new([pipeline]))
expect(service)
.to receive(:full_pipeline?)
.with(pipeline)
.and_return(false)
expect(service.send(:success_on_default_branch?, commit)).to be(false)
end
it 'returns false when the pipeline failed' do
expect(client)
.to receive(:pipelines)
.with(
project.auto_deploy_path,
{
sha: commit,
ref: project.default_branch,
status: 'success'
}
)
.and_return(Gitlab::PaginatedResponse.new([]))
expect(service.send(:success_on_default_branch?, commit)).to be(false)
end
end
end