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