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