spec/lib/release_tools/gitlab_client_spec.rb (1,149 lines of code) (raw):
# frozen_string_literal: true
require 'spec_helper'
describe ReleaseTools::GitlabClient do
describe 'internal client delegates' do
let(:internal_client) { instance_double(Gitlab::Client) }
let(:internal_graphql_client) { instance_double(ReleaseTools::GraphqlAdapter) }
before do
allow(described_class).to receive_messages(client: internal_client, graphql_client: internal_graphql_client)
end
it 'delegates .job_play' do
expect(internal_client).to receive(:job_play)
described_class.job_play('foo', 'bar')
end
end
describe '.current_user' do
after do
# HACK: Prevent cross-test pollution with the `.approve_merge_request` spec
described_class.instance_variable_set(:@current_user, nil)
end
it 'returns the current user', vcr: { cassette_name: 'current_user' } do
expect(described_class.current_user).not_to be_nil
end
end
describe '.pipelines', vcr: { cassette_name: 'pipelines' } do
it 'returns project pipelines' do
response = described_class.pipelines
expect(response.map(&:web_url)).to all(include('/pipelines/'))
end
end
describe '.pipeline', vcr: { cassette_name: 'pipeline' } do
it 'returns project pipeline' do
pipeline_id = '55053803'
response = described_class.pipeline(ReleaseTools::Project::GitlabCe, pipeline_id)
expect(response.web_url).to include("/pipelines/#{pipeline_id}")
end
end
describe '.pipeline_jobs', vcr: { cassette_name: 'pipeline_jobs' } do
it 'returns pipeline jobs' do
response = described_class.pipeline_jobs(ReleaseTools::Project::GitlabCe, '55053803')
expect(response.map(&:web_url)).to all(include('/jobs/'))
end
end
describe '.pipeline_job_by_name', vcr: { cassette_name: 'pipeline_job_by_name' } do
it 'returns first pipeline job by name' do
job_name = 'setup-test-env'
response = described_class.pipeline_job_by_name(ReleaseTools::Project::GitlabCe, '55053803', job_name)
expect(response.name).to eq(job_name)
end
end
describe '.job_trace', vcr: { cassette_name: 'job_trace' } do
it 'returns job trace' do
response = described_class.job_trace(ReleaseTools::Project::GitlabCe, '189985934')
expect(response).to include('mkdir -p rspec_flaky/')
end
end
describe '.milestones', vcr: { cassette_name: 'merge_requests/with_milestone' } do
it 'returns a combination of project and group milestones' do
skip "See https://gitlab.com/gitlab-com/gl-infra/delivery/-/issues/1032"
response = described_class.milestones
expect(response.map(&:title)).to include('9.4', '10.4')
end
end
describe '.current_milestone' do
it 'fetches the current milestone' do
current = build(:gitlab_response, title: '42.0')
expect(ReleaseTools::ProductMilestone).to receive(:current)
.and_return(current)
expect(described_class.current_milestone).to eq(current)
end
it 'falls back to MissingMilestone' do
expect(ReleaseTools::ProductMilestone).to receive(:current)
.and_return(nil)
expect(described_class.current_milestone).to be_a(described_class::MissingMilestone)
end
end
describe '.milestone', vcr: { cassette_name: 'merge_requests/with_milestone' } do
context 'when the milestone title is nil' do
it 'returns a MissingMilestone' do
skip "See https://gitlab.com/gitlab-com/gl-infra/delivery/-/issues/1032"
milestone = described_class.milestone(title: nil)
expect(milestone).to be_a(described_class::MissingMilestone)
expect(milestone.id).to be_nil
end
end
context 'when the milestone exists' do
it 'returns the milestone' do
skip "See https://gitlab.com/gitlab-com/gl-infra/delivery/-/issues/1032"
response = described_class.milestone(title: '9.4')
expect(response.title).to eq('9.4')
end
end
context 'when the milestone does not exist' do
it 'raises an exception' do
skip "See https://gitlab.com/gitlab-com/gl-infra/delivery/-/issues/1032"
expect { described_class.milestone(title: 'not-existent') }.to raise_error('Milestone not-existent not found for project gitlab-org/gitlab-foss!')
end
end
end
describe '.accept_merge_request' do
before do
allow(described_class).to receive(:current_user).and_return(double(id: 42))
end
let(:merge_request) do
double(
project: double(path: 'gitlab-org/gitlab-foss'),
title: 'Upstream MR',
iid: '12345',
description: 'Hello world',
labels: 'CE upstream',
source_branch: 'feature',
target_branch: 'master',
milestone: nil)
end
let(:default_params) do
{
merge_when_pipeline_succeeds: true
}
end
it 'accepts a merge request against master on the GitLab CE project' do
expect(described_class.__send__(:client))
.to receive(:accept_merge_request).with(
ReleaseTools::Project::GitlabCe.path,
merge_request.iid,
default_params)
described_class.accept_merge_request(merge_request)
end
context 'when passing a project' do
it 'accepts a merge request in the given project' do
expect(described_class.__send__(:client))
.to receive(:accept_merge_request).with(
ReleaseTools::Project::GitlabEe.path,
merge_request.iid,
default_params)
described_class
.accept_merge_request(merge_request, ReleaseTools::Project::GitlabEe)
end
end
end
describe '.cancel_merge_when_pipeline_succeeds' do
it 'cancels the merge when pipeline succeeds' do
merge_request = build(:merge_request)
response = double(:response)
allow(described_class.client)
.to receive(:post)
.with("/projects/#{merge_request.project_id}/merge_requests/#{merge_request.iid}/cancel_merge_when_pipeline_succeeds")
.and_return(response)
expect(described_class.cancel_merge_when_pipeline_succeeds(merge_request)).to eq(response)
end
end
describe '.create_merge_request' do
before do
allow(described_class).to receive_messages(
current_user: double(id: current_user_id),
current_milestone: double(id: 1)
)
end
let(:current_user_id) { 42 }
let(:merge_request) do
double(
project: double(path: 'gitlab-org/gitlab-foss'),
title: 'Upstream MR',
description: 'Hello world',
labels: 'CE upstream',
source_branch: 'feature',
target_branch: 'master',
assignee_ids: nil,
milestone: nil)
end
let(:default_params) do
{
description: merge_request.description,
assignee_ids: [current_user_id],
labels: merge_request.labels,
source_branch: merge_request.source_branch,
target_branch: 'master',
milestone_id: 1,
remove_source_branch: true
}
end
it 'creates a merge request against master on the GitLab CE project' do
expect(described_class.__send__(:client))
.to receive(:create_merge_request).with(
ReleaseTools::Project::GitlabCe.path,
merge_request.title,
default_params)
described_class.create_merge_request(merge_request)
end
context 'when passing a project' do
it 'creates a merge request in the given project' do
expect(described_class.__send__(:client))
.to receive(:create_merge_request).with(
ReleaseTools::Project::GitlabEe.path,
merge_request.title,
default_params)
described_class.create_merge_request(merge_request, ReleaseTools::Project::GitlabEe)
end
end
context 'when merge request has a target branch' do
before do
allow(merge_request).to receive(:target_branch).and_return('stable')
end
it 'creates a merge request against the given target branch' do
expect(described_class.__send__(:client))
.to receive(:create_merge_request).with(
ReleaseTools::Project::GitlabEe.path,
merge_request.title,
default_params.merge(target_branch: 'stable'))
described_class.create_merge_request(merge_request, ReleaseTools::Project::GitlabEe)
end
end
context 'with milestone', vcr: { cassette_name: 'merge_requests/with_milestone' } do
it 'sets milestone id' do
skip "See https://gitlab.com/gitlab-com/gl-infra/delivery/-/issues/1032"
allow(merge_request).to receive(:milestone).and_return('9.4')
response = described_class.create_merge_request(merge_request)
expect(response.milestone.title).to eq '9.4'
end
end
context 'when assignee_ids is nil' do
it 'defaults to current_user as assignee' do
expect(described_class.__send__(:client))
.to receive(:create_merge_request)
.with(anything, anything, default_params)
described_class.create_merge_request(merge_request)
end
end
context 'when assignee_ids is not nil' do
before do
assignee_ids = [1, 2]
allow(merge_request).to receive(:assignee_ids).and_return(assignee_ids)
default_params[:assignee_ids] = assignee_ids
end
it 'adds assignee_ids to params' do
expect(described_class.__send__(:client))
.to receive(:create_merge_request)
.with(anything, anything, default_params)
described_class.create_merge_request(merge_request)
end
end
end
describe '.find_issue' do
context 'when issue is open' do
it 'finds issues by title', vcr: { cassette_name: 'issues/release-8-7' } do
version = double(milestone_name: '8.7')
issue = double(title: 'Release 8.7', labels: 'Release', version: version)
expect(described_class.find_issue(issue)).not_to be_nil
end
end
context 'when issue is closed' do
it 'finds issues by title', vcr: { cassette_name: 'issues/regressions-8-5' } do
version = double(milestone_name: '8.5')
issue = double(title: '8.5 Regressions', labels: 'Release', state_filter: nil, version: version)
expect(described_class.find_issue(issue)).not_to be_nil
end
end
context 'when issue version is nil' do
let(:issue) do
build(:issue,
title: 'Release 8.5.7',
labels: ['devops::release'],
state: 'opened',
version: nil,
state_filter: nil
)
end
before do
allow(described_class).to receive(:issues).and_return([issue])
end
it 'finds issues by title', vcr: { cassette_name: 'issues/nil-version' } do
expect(described_class.find_issue(issue)).not_to be_nil
end
it 'exclude milestone from search', vcr: { cassette_name: 'issues/nil-version' } do
expect(described_class).to receive(:issues).with(anything, hash_excluding(:milestone)).and_call_original
described_class.find_issue(issue)
end
end
context 'when issue cannot be found' do
it 'does not find non-matching issues', vcr: { cassette_name: 'issues/release-7-14' } do
version = double(milestone_name: '7.14')
issue = double(title: 'Release 7.14', labels: 'Release', version: version)
expect(described_class.find_issue(issue)).to be_nil
end
end
end
context 'issue links' do
let(:internal_client) { instance_double(Gitlab::Client) }
let(:issue_project) { ReleaseTools::Project::GitlabCe }
let(:issue) do
instance_double(
ReleaseTools::PatchRelease::Issue,
project: issue_project,
iid: 1
)
end
before do
allow(described_class).to receive(:client).and_return(internal_client)
end
describe '.link_issues' do
let(:target) do
instance_double(
ReleaseTools::MonthlyIssue,
project: target_project,
iid: 2
)
end
let(:target_project) { ReleaseTools::Project::GitlabEe }
it 'links an issue to a target' do
allow(internal_client).to receive(:url_encode)
.with('gitlab-org/gitlab-foss')
.and_return('gitlab-org%2Fgitlab-foss')
expect(internal_client).to receive(:post).with(
'/projects/gitlab-org%2Fgitlab-foss/issues/1/links',
{ query: { target_project_id: 'gitlab-org/gitlab', target_issue_iid: 2 } }
)
described_class.link_issues(issue, target)
end
end
describe '.delete_issue_link' do
it 'deletes an issue link' do
issue_link = 111
expect(internal_client).to receive(:delete_issue_link).with(
issue.project.path,
issue.iid,
issue_link
)
described_class.delete_issue_link(issue, issue_link)
end
end
end
describe '.find_branch' do
it 'finds existing branches', vcr: { cassette_name: 'branches/9-4-stable' } do
expect(described_class.find_branch('9-4-stable').name).to eq '9-4-stable'
end
it "returns nil when branch can't be found", vcr: { cassette_name: 'branches/9-4-stable-doesntexist' } do
expect(described_class.find_branch('9-4-stable-doesntexist')).to be_nil
end
end
describe '.create_branch' do
it 'creates a branch', vcr: { cassette_name: 'branches/create-test' } do
branch_name = 'test-branch-from-release-tools'
response = described_class.create_branch(branch_name, 'master')
expect(response.name).to eq branch_name
end
end
describe '.cherry_pick' do
context 'on a successful pick' do
it 'returns a reasonable response', vcr: { cassette_name: 'cherry_pick/success' } do
ref = '59af98f133ee229479c6159b15391deb4782a294'
response = described_class.cherry_pick(ref: ref, target: '11-4-stable')
expect(response.message).to include("cherry picked from commit #{ref}")
end
it 'allows passing of a custom commit message' do
expect(described_class.client).to receive(:cherry_pick_commit).with(
ReleaseTools::Project::GitlabCe.path,
'123abc',
'master',
{
dry_run: false,
message: 'foo'
}
)
described_class
.cherry_pick(ref: '123abc', target: 'master', message: 'foo')
end
it 'does not pass the message argument to the API when left out' do
expect(described_class.client).to receive(:cherry_pick_commit).with(
ReleaseTools::Project::GitlabCe.path,
'123abc',
'master',
{ dry_run: false }
)
described_class.cherry_pick(ref: '123abc', target: 'master')
end
end
context 'on a failed pick' do
it 'raises an exception', vcr: { cassette_name: 'cherry_pick/failure' } do
expect do
described_class.cherry_pick(
ref: '396d205e5a503f9f48c223804087a80f7acc6d06',
target: '11-4-stable'
)
end.to raise_error(Gitlab::Error::BadRequest)
end
end
context 'on an invalid ref' do
it 'raises an `ArgumentError`' do
expect { described_class.cherry_pick(ref: '', target: 'foo') }
.to raise_error(ArgumentError)
end
end
end
describe '.add_to_merge_train' do
let(:merge_request) { double(iid: 42, project_id: 12) }
let(:sha) { 'abc123' }
it 'performs a GraphQL query and return the status' do
internal_graphql_client = instance_double(ReleaseTools::GraphqlAdapter)
allow(described_class).to receive(:graphql_client).and_return(internal_graphql_client)
expect(internal_graphql_client)
.to receive(:query)
.with(ReleaseTools::GitlabGraphqlQueries::ADD_TO_MERGE_TRAIN_QUERY, { project: merge_request.project_id, mr: merge_request.iid.to_s, sha: sha })
.and_return(double(errors: [], merge_request_accept: double(merge_request: double(auto_merge_enabled: true))))
expect(described_class.add_to_merge_train(merge_request, sha)).to be(true)
end
context 'when query return errors' do
it 'performs a GraphQL query and raises an error', graphql_cassette: 'merge_trains/add-error' do
expect { described_class.add_to_merge_train(merge_request, sha) }.to raise_error(%|MR couldn't be added to Merge Train: {"mergeRequestAccept":["The resource that you are attempting to access does not exist or you don't have permission to perform this action"]}|)
end
end
context 'when using GitLab API' do
it 'adds the merge request to the merge train' do
merge_request = create(:merge_request, iid: 1, project_id: 123, sha: 'abcdef')
response = double(:response)
allow(described_class.client)
.to receive(:post)
.with(
'/projects/123/merge_trains/merge_requests/1',
{
body: {
sha: merge_request.sha,
when_pipeline_succeeds: false
}
}
).and_return(response)
expect(described_class.add_to_merge_train(merge_request, merge_request.sha, graphql: false))
.to eq(response)
end
end
end
describe '.project_path' do
it 'returns the correct project path' do
project = double(path: 'foo/bar')
expect(described_class.project_path(project)).to eq 'foo/bar'
end
it 'returns a String unmodified' do
project = 'gitlab-org/security/gitlab'
expect(described_class.project_path(project)).to eq(project)
end
end
describe '.related_merge_requests' do
it 'returns the related merge requests' do
page = double(:page)
allow(described_class.client)
.to receive(:get)
.with('/projects/foo%2Fbar/issues/1/related_merge_requests')
.and_return(page)
expect(described_class.related_merge_requests('foo/bar', 1)).to eq(page)
end
end
describe '#tree' do
it 'lists the contents of a directory' do
project = ReleaseTools::Project::GitlabCe
tree = double(:tree)
allow(described_class.client)
.to receive(:tree)
.with(project.path, 'foo')
.and_return(tree)
expect(described_class.tree(project, 'foo')).to eq(tree)
end
end
describe '#get_file' do
it 'gets a file from the repository' do
project = ReleaseTools::Project::GitlabCe
file = double(:file)
allow(described_class.client)
.to receive(:get_file)
.with(project.path, 'foo', 'bar')
.and_return(file)
expect(described_class.get_file(project, 'foo', 'bar')).to eq(file)
end
end
describe '#find_or_create_branch' do
context 'when the branch exists' do
it 'returns the branch' do
branch = double(:branch)
allow(described_class)
.to receive(:find_branch)
.with('foo', ReleaseTools::Project::GitlabCe)
.and_return(branch)
expect(described_class.find_or_create_branch('foo', 'master'))
.to eq(branch)
end
end
context 'when the branch does not exist' do
it 'creates the branch' do
branch = double(:branch)
master = double(:branch, commit: double(:commit, id: '123'))
allow(described_class)
.to receive(:find_branch)
.with('foo', ReleaseTools::Project::GitlabCe)
.and_return(nil)
allow(described_class)
.to receive(:find_branch)
.with('master', ReleaseTools::Project::GitlabCe)
.and_return(master)
allow(described_class)
.to receive(:create_branch)
.with('foo', 'master', ReleaseTools::Project::GitlabCe)
.and_return(branch)
expect(described_class.find_or_create_branch('foo', 'master'))
.to eq(branch)
expect(described_class)
.to have_received(:create_branch)
end
end
end
describe '#find_or_create_tag' do
context 'when the tag exists' do
it 'returns the tag' do
tag = double(:tag)
project = ReleaseTools::Project::GitlabCe
allow(described_class)
.to receive(:tag)
.with(project, { tag: 'foo' })
.and_return(tag)
expect(described_class.find_or_create_tag(project, 'foo', 'master'))
.to eq(tag)
end
end
context 'when the tag does not exist' do
it 'creates the tag' do
tag = double(:tag)
project = ReleaseTools::Project::GitlabCe
allow(described_class)
.to receive(:tag)
.with(project, { tag: 'foo' })
.and_raise(gitlab_error(:NotFound))
allow(described_class)
.to receive(:create_tag)
.with(project, 'foo', 'master', nil)
.and_return(tag)
expect(described_class.find_or_create_tag(project, 'foo', 'master'))
.to eq(tag)
end
it 'supports creating annotated tags' do
tag = double(:tag)
project = ReleaseTools::Project::GitlabCe
allow(described_class)
.to receive(:tag)
.with(project, { tag: 'foo' })
.and_raise(gitlab_error(:NotFound))
allow(described_class)
.to receive(:create_tag)
.with(project, 'foo', 'master', 'foo')
.and_return(tag)
output = described_class
.find_or_create_tag(project, 'foo', 'master', message: 'foo')
expect(output).to eq(tag)
end
end
end
describe '#create_tag' do
it 'creates a tag' do
project = ReleaseTools::Project::GitlabCe
tag = double(:tag)
expect(described_class.client)
.to receive(:create_tag)
.with(project.path, 'foo', 'master')
.and_return(tag)
expect(described_class.create_tag(project, 'foo', 'master')).to eq(tag)
end
end
describe '#branches' do
it 'returns the branches for a project' do
response = double(:response)
expect(described_class.client)
.to receive(:branches)
.with('gitlab-org/gitlab', { search: 'foo' })
.and_return(response)
expect(described_class.branches(ReleaseTools::Project::GitlabEe, search: 'foo'))
.to eq(response)
end
end
describe '.create_merge_request_pipeline' do
it 'triggers a merge request pipeline' do
response = double(:response)
allow(described_class.client)
.to receive(:post)
.with('/projects/123/merge_requests/1/pipelines')
.and_return(response)
expect(described_class.create_merge_request_pipeline(123, 1)).to eq(response)
end
end
describe '#tags' do
it 'returns the tags of a project' do
response = double(:response)
expect(described_class.client)
.to receive(:tags)
.with('gitlab-org/gitlab', { search: 'foo' })
.and_return(response)
expect(described_class.tags(ReleaseTools::Project::GitlabEe, search: 'foo'))
.to eq(response)
end
end
describe '.create_issue' do
let(:version) { double(:version, milestone_name: '13.6') }
let(:milestone) { double(:milestone, id: '123') }
let(:current_user) { double(:user, id: '123') }
let(:issue) do
double(:issue,
title: 'foo',
description: 'bar',
confidential?: true,
version: version,
labels: 'baz')
end
let(:default_options) do
{
description: issue.description,
assignee_ids: [current_user.id],
milestone_id: milestone.id,
labels: issue.labels,
confidential: issue.confidential?
}
end
subject(:create_issue) { described_class.create_issue(issue) }
before do
allow(described_class)
.to receive_messages(milestone: milestone, current_user: current_user)
end
it 'creates an issue on the project' do
expect(described_class.client)
.to receive(:create_issue).with(
ReleaseTools::Project::GitlabCe.path,
issue.title,
default_options
)
create_issue
end
context 'with nil version' do
let(:version) { nil }
it 'creates an issue on the project' do
expect(described_class.client)
.to receive(:create_issue).with(
ReleaseTools::Project::GitlabCe.path,
issue.title,
default_options.except(:milestone_id)
)
create_issue
end
end
context 'when the issue responds to assignees' do
let(:issue) do
double(:issue,
title: 'foo',
description: 'bar',
confidential?: true,
version: version,
labels: 'baz',
assignees: [456, 789])
end
it 'creates an issue with specific assignees' do
options = default_options.merge(assignee_ids: [456, 789])
expect(described_class.client)
.to receive(:create_issue).with(
ReleaseTools::Project::GitlabCe.path,
issue.title,
options
)
create_issue
end
end
context 'when the issue responds to due_date' do
let(:issue) do
double(:issue,
title: 'foo',
description: 'bar',
confidential?: true,
version: version,
labels: 'baz',
due_date: '2020-01-01')
end
it 'creates an issue with the due date' do
options = default_options.merge(due_date: '2020-01-01')
expect(described_class.client)
.to receive(:create_issue).with(
ReleaseTools::Project::GitlabCe.path,
issue.title,
options
)
create_issue
end
end
end
describe '.update_issue' do
let(:version) { double(:version, milestone_name: '13.6') }
let(:milestone) { double(:milestone, id: '123') }
let(:issue) do
double(:issue,
iid: 1,
description: 'bar',
confidential?: true,
version: version,
labels: 'baz')
end
let(:default_options) do
{
description: issue.description,
milestone_id: milestone.id,
labels: issue.labels,
confidential: issue.confidential?
}
end
subject(:update_issue) { described_class.update_issue(issue) }
before do
allow(described_class)
.to receive(:milestone).and_return(milestone)
end
it 'updates the issue' do
expect(described_class.client)
.to receive(:edit_issue).with(
ReleaseTools::Project::GitlabCe.path,
issue.iid,
default_options
)
update_issue
end
context 'with nil version' do
let(:version) { nil }
it 'updates the issue' do
expect(described_class.client)
.to receive(:edit_issue).with(
ReleaseTools::Project::GitlabCe.path,
issue.iid,
default_options.except(:milestone_id)
)
update_issue
end
end
end
describe '.update_or_create_deployment' do
it 'updates an existing deployment of the same ref and SHA' do
project = double('Project')
environment = 'gprd'
expect(described_class).to receive(:deployments)
.with(project, environment, { status: 'running' })
.and_return([
double(id: 1, ref: 'master', sha: 'a', status: 'running'),
double(id: 2, ref: 'master', sha: 'b', status: 'running'),
double(id: 3, ref: 'master', sha: 'c', status: 'running'),
double(id: 4, ref: 'master', sha: 'd', status: 'running')
])
expect(described_class).to receive(:update_deployment)
.with(project, 3, { status: 'success' })
described_class.update_or_create_deployment(
project,
environment,
ref: 'master',
sha: 'c',
status: 'success'
)
end
it 'does not update existing deployment if it already has same status' do
project = double('Project')
environment = 'gprd'
existing_deployment = build(:deployment, id: 3, ref: 'master', sha: 'c', status: 'running')
expect(described_class).to receive(:deployments)
.with(project, environment, { status: 'running' })
.and_return([
build(:deployment, id: 1, ref: 'master', sha: 'a', status: 'running'),
build(:deployment, id: 2, ref: 'master', sha: 'b', status: 'running'),
existing_deployment,
build(:deployment, id: 4, ref: 'master', sha: 'd', status: 'running')
])
expect(described_class).not_to receive(:update_deployment)
expect(described_class).not_to receive(:create_deployment)
deployment = described_class.update_or_create_deployment(
project,
environment,
ref: 'master',
sha: 'c',
status: 'running'
)
expect(deployment).to eq(existing_deployment)
end
it 'creates a new deployment' do
project = double('Project')
environment = 'gprd'
expect(described_class).to receive(:deployments)
.with(project, environment, { status: 'running' })
.and_return([double(id: 1, ref: 'master', sha: 'a', status: 'running')])
expect(described_class).not_to receive(:update_deployment)
expect(described_class).to receive(:create_deployment)
.with(project, environment, { ref: 'master', sha: 'b', status: 'running', tag: false })
described_class.update_or_create_deployment(
project,
environment,
ref: 'master',
sha: 'b',
status: 'running'
)
end
end
describe '.compile_changelog' do
it 'compiles the changelog' do
expect(described_class.client).to receive(:post).with(
"/projects/foo%2Fbar/repository/changelog",
{
body: {
version: '1.1.0',
to: '123',
branch: 'main',
message: "Update changelog for 1.1.0\n\n[ci skip]"
}
}
)
described_class.compile_changelog('foo/bar', '1.1.0', '123', 'main')
end
it 'compiles the changelog without skipping ci' do
expect(described_class.client).to receive(:post).with(
"/projects/foo%2Fbar/repository/changelog",
{
body: {
version: '1.1.0',
to: '123',
branch: 'main',
message: "Update changelog for 1.1.0"
}
}
)
described_class.compile_changelog('foo/bar', '1.1.0', '123', 'main', skip_ci: false)
end
end
describe '.commit_status' do
it 'returns the commit statuses' do
response = double(:response)
expect(described_class.client)
.to receive(:commit_status)
.with('gitlab-org/gitlab', '123', { ref: 'master' })
.and_return(response)
result = described_class
.commit_status(ReleaseTools::Project::GitlabEe, '123', ref: 'master')
expect(result).to eq(response)
end
end
describe '.merge_request_commits' do
it 'returns the commits of a merge request' do
response = double(:response)
expect(described_class.client)
.to receive(:merge_request_commits)
.with(ReleaseTools::Project::GitlabCe.path, 42)
.and_return(response)
result = described_class
.merge_request_commits(ReleaseTools::Project::GitlabCe, 42)
expect(result).to eq(response)
end
end
describe '.create_pipeline' do
let(:project) { ReleaseTools::Project::GitlabEe }
let(:pipeline_variables) { { key: 'foo', value: 'bar' } }
it 'creates a pipeline' do
expect(described_class.client)
.to receive(:create_pipeline)
.with('gitlab-org/gitlab', 'master', pipeline_variables)
described_class.create_pipeline(project, pipeline_variables)
end
context 'when provided a branch' do
it 'creates a pipeline on that branch' do
expect(described_class.client)
.to receive(:create_pipeline)
.with('gitlab-org/gitlab', 'foo', pipeline_variables)
described_class.create_pipeline(project, pipeline_variables, branch: 'foo')
end
end
end
describe '.download_raw_job_artifact' do
it 'returns the raw version of the artifact' do
project = double(:project, canonical_id: 123)
expect(described_class.client).to receive(:get).with(
'/projects/123/jobs/456/artifacts/foo',
{
format: nil,
headers: { Accept: 'application/octet-stream' },
parser: Gitlab::Request::Parser
}
)
described_class.download_raw_job_artifact(project, 456, 'foo')
end
end
describe '.last_successful_deployment' do
it 'returns the last successful deployment for a given environment' do
project = ReleaseTools::Project::GitlabEe
environment = 'gprd'
expect(described_class.client).to receive(:deployments).with(
'gitlab-org/gitlab',
{
environment: environment,
status: 'success',
order_by: 'id',
sort: 'desc',
per_page: 1
}
).and_return([double(id: 1, ref: 'master', sha: 'a', status: 'success')])
described_class.last_successful_deployment(project.path, environment)
end
end
describe '.cancel_job' do
it 'cancels the job' do
project = ReleaseTools::Project::GitlabEe
job_id = '123'
expect(described_class.client)
.to receive(:job_cancel)
.with('gitlab-org/gitlab', '123')
described_class.cancel_job(project, job_id)
end
end
describe '.pipeline_variables' do
context 'with project class' do
it 'calls pipeline variables API' do
project = ReleaseTools::Project::GitlabEe
pipeline_id = '123'
path = 'gitlab-org%2Fgitlab'
expect(described_class.client)
.to receive(:get)
.with("/projects/#{path}/pipelines/#{pipeline_id}/variables")
described_class.pipeline_variables(project, pipeline_id)
end
end
context 'with project path' do
it 'calls pipeline variables API' do
project = 'gitlab-org/gitlab'
pipeline_id = '123'
path = 'gitlab-org%2Fgitlab'
expect(described_class.client)
.to receive(:get)
.with("/projects/#{path}/pipelines/#{pipeline_id}/variables")
described_class.pipeline_variables(project, pipeline_id)
end
end
context 'with project ID' do
it 'calls pipeline variables API' do
project = 20
pipeline_id = 123
expect(described_class.client)
.to receive(:get)
.with("/projects/#{project}/pipelines/#{pipeline_id}/variables")
described_class.pipeline_variables(project, pipeline_id)
end
end
end
describe '.pipeline_schedules' do
it 'calls the pipeline_schedules API method' do
project = ReleaseTools::Project::GitlabEe
expect(described_class.client)
.to receive(:pipeline_schedules)
.with('gitlab-org/gitlab')
described_class.pipeline_schedules(project)
end
end
describe '.pipeline_schedule' do
it 'calls the pipeline_schedule API method' do
project = ReleaseTools::Project::GitlabEe
pipeline_schedule_id = '123'
expect(described_class.client)
.to receive(:pipeline_schedule)
.with('gitlab-org/gitlab', '123')
described_class.pipeline_schedule(project, pipeline_schedule_id)
end
end
describe '.pipeline_schedule_take_ownership' do
it 'calls the pipeline_schedule_take_ownership API method' do
project = ReleaseTools::Project::GitlabEe
pipeline_schedule_id = '123'
expect(described_class.client)
.to receive(:pipeline_schedule_take_ownership)
.with('gitlab-org/gitlab', '123')
described_class.pipeline_schedule_take_ownership(project, pipeline_schedule_id)
end
end
describe '.run_pipeline_schedule' do
let(:pipeline_schedule_id) { 123 }
let(:project) { ReleaseTools::Project::GitlabEe }
it 'calls the run_pipeline_schedule API method' do
expect(described_class.client)
.to receive(:run_pipeline_schedule)
.with('gitlab-org/gitlab', pipeline_schedule_id)
described_class.run_pipeline_schedule(project, pipeline_schedule_id)
end
end
describe '.edit_pipeline_schedule' do
let(:project) { ReleaseTools::Project::GitlabEe }
let(:pipeline_schedule_id) { 123 }
let(:options) { { description: 'Updated schedule' } }
it 'calls client.edit_pipeline_schedule with correct arguments' do
expect(described_class.client)
.to receive(:edit_pipeline_schedule)
.with('gitlab-org/gitlab', pipeline_schedule_id, options)
described_class.edit_pipeline_schedule(project, pipeline_schedule_id, options)
end
it 'works with an empty options hash' do
expect(described_class.client)
.to receive(:edit_pipeline_schedule)
.with('gitlab-org/gitlab', pipeline_schedule_id, {})
described_class.edit_pipeline_schedule(project, pipeline_schedule_id)
end
end
describe '.edit_pipeline_schedule_variable' do
let(:project) { ReleaseTools::Project::GitlabEe }
let(:pipeline_schedule_id) { 123 }
let(:variable_key) { 'FOO' }
let(:options) { { value: "bar" } }
it 'calls client.edit_pipeline_schedule_variable with correct arguments' do
expect(described_class.client)
.to receive(:edit_pipeline_schedule_variable)
.with('gitlab-org/gitlab', pipeline_schedule_id, variable_key, options)
described_class.edit_pipeline_schedule_variable(project, pipeline_schedule_id, variable_key, options)
end
end
describe '.next_security_tracking_issue' do
it 'fetches the next security tracking issue' do
allow(described_class.client)
.to receive(:issues)
.and_return([create(:issue)])
expect(described_class.client)
.to receive(:issues)
.with('gitlab-org/gitlab', labels: 'upcoming security release', state: 'opened')
described_class.next_security_tracking_issue
end
end
describe '.current_security_task_issue' do
it 'fetches the current patch release task issue' do
allow(described_class.client)
.to receive(:issues)
.and_return([create(:issue)])
expect(described_class.client)
.to receive(:issues)
.with('gitlab-org/release/tasks', labels: ['security', 'Monthly Release'], state: 'opened')
described_class.current_security_task_issue
end
end
describe '.security_communication_issue' do
it 'fetches the security communication issue' do
allow(described_class.client)
.to receive(:issues)
.and_return([create(:issue)])
expect(described_class.client)
.to receive(:issues)
.with(
'gitlab-com/gl-security/security-communications/communications',
labels: 'Security Release Blog Alert::development',
state: 'opened'
)
described_class.security_communication_issue
end
end
describe '.security_blog_merge_request' do
let(:security) { true }
subject(:merge_request) { described_class.security_blog_merge_request(security: security) }
it 'fetches the patch release blog post from the security repository' do
allow(described_class.client)
.to receive(:merge_requests)
.and_return([create(:merge_request)])
expect(described_class.client)
.to receive(:merge_requests)
.with(ReleaseTools::Project::WWWGitlabCom.security_path, labels: 'patch release post', state: 'opened')
merge_request
end
context 'in the canonical repository' do
let(:security) { false }
it 'fetches the patch release blog post from the canonical repository' do
allow(described_class.client)
.to receive(:merge_requests)
.and_return([create(:merge_request)])
expect(described_class.client)
.to receive(:merge_requests)
.with(ReleaseTools::Project::WWWGitlabCom.path, labels: 'patch release post', state: 'opened')
merge_request
end
end
end
describe '.approve_merge_request' do
it 'calls the approval_merge_request API method' do
merge_request = create(:merge_request, iid: 123, project: ReleaseTools::Project::GitlabEe)
expect(described_class.client)
.to receive(:approve_merge_request)
.with('gitlab-org/gitlab', 123)
described_class.approve_merge_request(merge_request.project, merge_request.iid)
end
end
describe '.merge_request_approvals' do
it 'calls the merge_request_approvals API method' do
merge_request = create(:merge_request, iid: 123, project: ReleaseTools::Project::GitlabEe)
expect(described_class.client)
.to receive(:merge_request_approvals)
.with('gitlab-org/gitlab', 123)
described_class.merge_request_approvals(merge_request.project, merge_request.iid)
end
end
describe '.merge_request_pipelines' do
it 'calls the merge_request_pipelines API method' do
merge_request = create(:merge_request, iid: 123, project: ReleaseTools::Project::GitlabEe)
expect(described_class.client)
.to receive(:merge_request_pipelines)
.with('gitlab-org/gitlab', 123)
described_class.merge_request_pipelines(merge_request.project, merge_request.iid)
end
end
describe '.merge_request_diffs' do
it 'executes a GET call to retrieve the diff' do
page = double(:page)
allow(described_class.client)
.to receive(:get)
.with('/projects/foo%2Fbar/merge_requests/1/diffs')
.and_return(page)
expect(described_class.merge_request_diffs('foo/bar', 1)).to eq(page)
end
end
describe '.update_project_variable' do
it 'executes a PUT call to update the given variable' do
key = 'MY_VAR'
value = 'a value'
ci_variable = double(:ci_variable)
allow(described_class.client)
.to receive(:put)
.with("/projects/foo%2Fbar/variables/#{key}", body: { value: value })
.and_return(ci_variable)
expect(described_class.update_project_variable('foo/bar', key, value)).to eq(ci_variable)
end
end
describe '.project_access_tokens' do
it 'executes a GET call to fetch all the tokens' do
tokens = double(:tokens)
allow(described_class.client)
.to receive(:get)
.with('/projects/foo%2Fbar/access_tokens')
.and_return(tokens)
expect(described_class.project_access_tokens('foo/bar')).to eq(tokens)
end
end
describe '.rotate_project_access_token' do
it 'executes a POST call to rotate the token' do
key = 'MY_VAR'
expires_at = '2020-01-01'
result = double(:result)
allow(described_class.client)
.to receive(:post)
.with("/projects/foo%2Fbar/access_tokens/#{key}/rotate", body: { expires_at: expires_at })
.and_return(result)
expect(described_class.rotate_project_access_token('foo/bar', key, expires_at: expires_at)).to eq(result)
end
end
describe '.sync_remote_mirror' do
it 'calls the sync remote endpoint' do
result = double(:result)
allow(described_class.client)
.to receive(:post)
.with('/projects/foo%2Fbar/remote_mirrors/123/sync')
.and_return(result)
expect(described_class.sync_remote_mirror('foo/bar', 123)).to eq(result)
end
end
end