diff --git a/common/lib/dependabot/clients/azure.rb b/common/lib/dependabot/clients/azure.rb index 5338c92dbf..bc3a905fca 100644 --- a/common/lib/dependabot/clients/azure.rb +++ b/common/lib/dependabot/clients/azure.rb @@ -184,6 +184,28 @@ def create_pull_request(pr_name, source_branch, target_branch, "/pullrequests?api-version=5.0", content.to_json) end + def update_pull_request(pull_request_id:, status: nil, pr_name: nil, description: nil, + completion_options: nil, merge_options: nil, + auto_complete_setby_user_id: nil, target_branch: nil) + content = { + autoCompleteSetBy: ({ id: auto_complete_setby_user_id } unless + auto_complete_setby_user_id.nil? || auto_complete_setby_user_id.strip.empty?), + title: pr_name, + description: truncate_pr_description(description), + status: status, + completionOptions: completion_options, + mergeOptions: merge_options, + targetRefName: ("refs/heads/" + target_branch unless target_branch.nil? || target_branch.strip.empty?) + }.compact + + return if content.empty? + + patch(source.api_endpoint + + source.organization + "/" + source.project + + "/_apis/git/repositories/" + source.unscoped_repo + + "/pullrequests/" + pull_request_id.to_s + "?api-version=5.0", content.to_json) + end + def pull_request(pull_request_id) response = get(source.api_endpoint + source.organization + "/" + source.project + @@ -251,6 +273,26 @@ def post(url, json) response end + def patch(url, json) + response = Excon.patch( + url, + body: json, + user: credentials&.fetch("username", nil), + password: credentials&.fetch("password", nil), + idempotent: true, + **SharedHelpers.excon_defaults( + headers: auth_header.merge( + { + "Content-Type" => "application/json" + } + ) + ) + ) + raise NotFound if response.status == 404 + + response + end + private def retry_connection_failures @@ -279,6 +321,8 @@ def auth_header_for(token) end def truncate_pr_description(pr_description) + return if pr_description.nil? || pr_description.empty? + # Azure DevOps only support descriptions up to 4000 characters in UTF-16 # encoding. # https://developercommunity.visualstudio.com/content/problem/608770/remove-4000-character-limit-on-pull-request-descri.html diff --git a/common/lib/dependabot/pull_request_updater/azure.rb b/common/lib/dependabot/pull_request_updater/azure.rb index d2a506b7c9..693d967f94 100644 --- a/common/lib/dependabot/pull_request_updater/azure.rb +++ b/common/lib/dependabot/pull_request_updater/azure.rb @@ -27,6 +27,21 @@ def update update_source_branch end + def update_pr_elements(elements = {}) + return unless elements + + azure_client_for_source.update_pull_request( + pull_request_id: pull_request_number, + status: elements[:status], + pr_name: elements[:pr_name], + description: elements[:description], + completion_options: elements[:completion_options], + merge_options: elements[:merge_options], + auto_complete_setby_user_id: elements[:auto_complete_setby_user_id], + target_branch: elements[:target_branch] + ) + end + private def azure_client_for_source diff --git a/common/spec/dependabot/clients/azure_spec.rb b/common/spec/dependabot/clients/azure_spec.rb index e93e9d1523..4a7e174a01 100644 --- a/common/spec/dependabot/clients/azure_spec.rb +++ b/common/spec/dependabot/clients/azure_spec.rb @@ -210,6 +210,60 @@ end end + describe "#update_pull_request" do + subject(:update_pull_request) do + client.update_pull_request( + pull_request_id: pull_request_id, + auto_complete_setby_user_id: auto_complete_setby_user_id, + pr_name: pr_name + ) + end + + let(:pull_request_id) { 1 } + let(:auto_complete_setby_user_id) { "id" } + let(:pr_name) { "Test PR" } + let(:update_pull_request_url) { repo_url + "/pullrequests/" + pull_request_id.to_s + "?api-version=5.0" } + + it "returns nil when there are no parameters to update" do + response = client.update_pull_request(pull_request_id: pull_request_id) + + expect(response).to be_nil + end + + context "sends update PR request with updated parameter values" do + it "successfully updates the PR" do + stub_request(:patch, update_pull_request_url). + with(basic_auth: [username, password]). + to_return(status: 200) + + update_pull_request + + expect(WebMock). + to( + have_requested(:patch, update_pull_request_url). + with do |req| + pr_update_details = JSON.parse(req.body) + expect(pr_update_details). + to eq( + { + "title" => pr_name, + "autoCompleteSetBy" => { "id" => auto_complete_setby_user_id } + } + ) + end + ) + end + + it "raises helpful error when response is 404" do + stub_request(:patch, update_pull_request_url). + with(basic_auth: [username, password]). + to_return(status: 404) + + expect { update_pull_request }.to raise_error(Dependabot::Clients::Azure::NotFound) + end + end + end + describe "#get" do context "Using auth headers" do token = ":test_token" diff --git a/common/spec/dependabot/pull_request_updater/azure_spec.rb b/common/spec/dependabot/pull_request_updater/azure_spec.rb index 7e3fbf8371..741eb7b5ff 100644 --- a/common/spec/dependabot/pull_request_updater/azure_spec.rb +++ b/common/spec/dependabot/pull_request_updater/azure_spec.rb @@ -166,4 +166,39 @@ ) end end + + describe "#update_pr_elements" do + let(:update_pull_request_url) { repo_url + "/pullrequests/" + pull_request_number.to_s + "?api-version=5.0" } + + context "returns nil" do + it "when elements hash is empty" do + expect(updater.update_pr_elements).to be_nil + end + + it "when elements hash does not contain expected parameters for update" do + elements = { test_parameter: "test" } + + expect(updater.update_pr_elements(elements)).to be_nil + end + end + + it "updates the given pr elements" do + elements = { pr_name: "Test PR", auto_complete_setby_user_id: "id" } + + stub_request(:patch, update_pull_request_url). + to_return(status: 200) + + updater.update_pr_elements(elements) + + expect(WebMock). + to( + have_requested(:patch, update_pull_request_url). + with(body: + { + title: elements[:pr_name], + autoCompleteSetBy: { id: elements[:auto_complete_setby_user_id] } + }) + ) + end + end end