diff --git a/common/lib/dependabot/errors.rb b/common/lib/dependabot/errors.rb index 8597d4fe8c..467fe30b87 100644 --- a/common/lib/dependabot/errors.rb +++ b/common/lib/dependabot/errors.rb @@ -220,6 +220,11 @@ def self.updater_error_details(error) "file-path": error.file_path } } + when Dependabot::DependencyFileNotSupported + { + "error-type": "dependency_file_not_supported", + "error-detail": { message: error.message } + } when Dependabot::GitDependenciesNotReachable { "error-type": "git_dependencies_not_reachable", @@ -616,6 +621,8 @@ class DependencyFileNotEvaluatable < DependabotError; end class DependencyFileNotResolvable < DependabotError; end + class DependencyFileNotSupported < DependabotError; end + class BadRequirementError < Gem::Requirement::BadRequirementError; end ####################### diff --git a/devcontainers/spec/dependabot/devcontainers/file_parser_spec.rb b/devcontainers/spec/dependabot/devcontainers/file_parser_spec.rb index ac6ce44561..1d8e7bcd43 100644 --- a/devcontainers/spec/dependabot/devcontainers/file_parser_spec.rb +++ b/devcontainers/spec/dependabot/devcontainers/file_parser_spec.rb @@ -234,7 +234,7 @@ it "returns the correct package manager" do expect(package_manager.name).to eq "devcontainers" expect(package_manager.requirement).to be_nil - expect(package_manager.version.to_s).to eq "0.72.0" + expect(package_manager.version.to_s).to match(/\d+.\d+.\d+/) end end @@ -244,7 +244,7 @@ it "returns the correct language" do expect(language.name).to eq "node" expect(language.requirement).to be_nil - expect(language.version.to_s).to eq "18.20.6" + expect(language.version.to_s).to match(/\d+.\d+.\d+/) end end end diff --git a/updater/lib/dependabot/api_client.rb b/updater/lib/dependabot/api_client.rb index fa879c888b..3fb27db3ef 100644 --- a/updater/lib/dependabot/api_client.rb +++ b/updater/lib/dependabot/api_client.rb @@ -5,6 +5,7 @@ require "dependabot/job" require "dependabot/opentelemetry" require "sorbet-runtime" +require "dependabot/errors" # Provides a client to access the internal Dependabot Service's API # @@ -18,10 +19,11 @@ module Dependabot class ApiError < StandardError; end - class ApiClient + class ApiClient # rubocop:disable Metrics/ClassLength extend T::Sig MAX_REQUEST_RETRIES = 3 + INVALID_REQUEST_MSG = /The request contains invalid or unauthorized changes/ sig { params(base_url: String, job_id: T.any(String, Integer), job_token: String).void } def initialize(base_url, job_id, job_token) @@ -41,7 +43,12 @@ def create_pull_request(dependency_change, base_commit_sha) api_url = "#{base_url}/update_jobs/#{job_id}/create_pull_request" data = create_pull_request_data(dependency_change, base_commit_sha) response = http_client.post(api_url, json: { data: data }) - raise ApiError, response.body if response.code >= 400 + + if response.code >= 400 && dependency_file_not_supported_error?(response.body.to_s) + raise Dependabot::DependencyFileNotSupported, response.body.to_s + elsif response.code >= 400 + raise ApiError, response.body + end rescue HTTP::ConnectionError, OpenSSL::SSL::SSLError retry_count ||= 0 retry_count += 1 @@ -416,5 +423,15 @@ def create_pull_request_data(dependency_change, base_commit_sha) data["pr-body"] = dependency_change.pr_message.pr_message data end + + sig { params(response: String).returns(T::Boolean) } + def dependency_file_not_supported_error?(response) + body = JSON.parse(response) + + return false unless body.is_a?(Hash) + return false unless body["errors"] + + INVALID_REQUEST_MSG.match? body["errors"].first["detail"] + end end end diff --git a/updater/spec/dependabot/api_client_spec.rb b/updater/spec/dependabot/api_client_spec.rb index 45fcc686ab..439ba5eb15 100644 --- a/updater/spec/dependabot/api_client_spec.rb +++ b/updater/spec/dependabot/api_client_spec.rb @@ -214,6 +214,28 @@ end) end end + + context "when API returns a 400 Bad Request" do + let(:body) do + <<~ERROR + { "errors": [{ + "status": 400, + "title": "Bad Request", + "detail": "The request contains invalid or unauthorized changes"}] + } + ERROR + end + + before do + stub_request(:post, create_pull_request_url).to_return(status: 400, body: body) + end + + it "raises the correct error" do + expect do + client.create_pull_request(dependency_change, base_commit) + end.to raise_error(Dependabot::DependencyFileNotSupported) + end + end end describe "update_pull_request" do