diff --git a/npm_and_yarn/lib/dependabot/npm_and_yarn.rb b/npm_and_yarn/lib/dependabot/npm_and_yarn.rb index 3c1cb3be7a6..44fd29d7843 100644 --- a/npm_and_yarn/lib/dependabot/npm_and_yarn.rb +++ b/npm_and_yarn/lib/dependabot/npm_and_yarn.rb @@ -56,6 +56,10 @@ module NpmAndYarn # Used to check if error message contains timeout fetching package TIMEOUT_FETCHING_PACKAGE_REGEX = %r{(?.+)/(?[^/]+): ETIMEDOUT} + ESOCKETTIMEDOUT = /(?.*?): ESOCKETTIMEDOUT/ + + SOCKET_HANG_UP = /(?.*?): socket hang up/ + # Used to identify git unreachable error UNREACHABLE_GIT_CHECK_REGEX = /ls-remote --tags --heads (?.*)/ @@ -104,6 +108,7 @@ module NpmAndYarn # Used to identify if authentication failure error AUTHENTICATION_TOKEN_NOT_PROVIDED = "authentication token not provided" AUTHENTICATION_IS_NOT_CONFIGURED = "No authentication configured for request" + AUTHENTICATION_HEADER_NOT_PROVIDED = "Unauthenticated: request did not include an Authorization header." # Used to identify if error message is related to yarn workspaces DEPENDENCY_FILE_NOT_RESOLVABLE = "conflicts with direct dependency" @@ -317,7 +322,8 @@ def self.sanitize_resolvability_message(error_message, dependencies, yarn_lock) matchfn: nil }, { - patterns: [AUTHENTICATION_TOKEN_NOT_PROVIDED, AUTHENTICATION_IS_NOT_CONFIGURED], + patterns: [AUTHENTICATION_TOKEN_NOT_PROVIDED, AUTHENTICATION_IS_NOT_CONFIGURED, + AUTHENTICATION_HEADER_NOT_PROVIDED], handler: lambda { |message, _error, _params| Dependabot::PrivateSourceAuthenticationFailure.new(message) }, @@ -359,7 +365,28 @@ def self.sanitize_resolvability_message(error_message, dependencies, yarn_lock) }, in_usage: false, matchfn: nil + }, + { + patterns: [SOCKET_HANG_UP], + handler: lambda { |message, _error, _params| + url = message.match(SOCKET_HANG_UP).named_captures.fetch(URL_CAPTURE) + + Dependabot::PrivateSourceTimedOut.new(url.gsub(HTTP_CHECK_REGEX, "")) + }, + in_usage: false, + matchfn: nil + }, + { + patterns: [ESOCKETTIMEDOUT], + handler: lambda { |message, _error, _params| + package_req = message.match(ESOCKETTIMEDOUT).named_captures.fetch("package") + + Dependabot::PrivateSourceTimedOut.new(package_req.gsub(HTTP_CHECK_REGEX, "")) + }, + in_usage: false, + matchfn: nil } + ].freeze, T::Array[{ patterns: T::Array[T.any(String, Regexp)], handler: ErrorHandler, diff --git a/npm_and_yarn/lib/dependabot/npm_and_yarn/file_parser.rb b/npm_and_yarn/lib/dependabot/npm_and_yarn/file_parser.rb index f22fe160beb..e78b6c23a81 100644 --- a/npm_and_yarn/lib/dependabot/npm_and_yarn/file_parser.rb +++ b/npm_and_yarn/lib/dependabot/npm_and_yarn/file_parser.rb @@ -166,7 +166,7 @@ def build_dependency(file:, type:, name:, requirement:) sig { override.void } def check_required_files - raise "No package.json!" unless get_original_file("package.json") + raise DependencyFileNotFound.new(nil, "package.json not found.") unless get_original_file("package.json") end sig { params(requirement: String).returns(T::Boolean) } diff --git a/npm_and_yarn/lib/dependabot/npm_and_yarn/file_updater.rb b/npm_and_yarn/lib/dependabot/npm_and_yarn/file_updater.rb index 051e07a31d6..7cfac79f307 100644 --- a/npm_and_yarn/lib/dependabot/npm_and_yarn/file_updater.rb +++ b/npm_and_yarn/lib/dependabot/npm_and_yarn/file_updater.rb @@ -139,7 +139,7 @@ def filtered_dependency_files sig { override.void } def check_required_files - raise "No package.json!" unless get_original_file("package.json") + raise DependencyFileNotFound.new(nil, "package.json not found.") unless get_original_file("package.json") end sig { params(updated_files: T::Array[DependencyFile]).returns(T::Hash[Symbol, T.untyped]) } diff --git a/npm_and_yarn/lib/dependabot/npm_and_yarn/file_updater/npm_lockfile_updater.rb b/npm_and_yarn/lib/dependabot/npm_and_yarn/file_updater/npm_lockfile_updater.rb index dd6d0d88e7c..1e1537df851 100644 --- a/npm_and_yarn/lib/dependabot/npm_and_yarn/file_updater/npm_lockfile_updater.rb +++ b/npm_and_yarn/lib/dependabot/npm_and_yarn/file_updater/npm_lockfile_updater.rb @@ -89,6 +89,7 @@ def updated_lockfile_reponse(response) ERROR_E401 = /code E401/ ERROR_E403 = /code E403/ ERROR_EAI_AGAIN = /request to (?.*) failed, reason: getaddrinfo EAI_AGAIN/ + PACKAGE_DISCOVERY_FAIL = /Couldn't find package "(?.*)" *.* on the "(?.*)" registry./ # TODO: look into fixing this in npm, seems like a bug in the git # downloader introduced in npm 7 @@ -575,6 +576,8 @@ def handle_npm_updater_error(error) raise Dependabot::DependencyFileNotResolvable, msg end + raise Dependabot::DependencyFileNotResolvable, error_message if error_message.match(PACKAGE_DISCOVERY_FAIL) + raise error end # rubocop:enable Metrics/AbcSize diff --git a/npm_and_yarn/lib/dependabot/npm_and_yarn/file_updater/yarn_lockfile_updater.rb b/npm_and_yarn/lib/dependabot/npm_and_yarn/file_updater/yarn_lockfile_updater.rb index 77ec27fbb1b..21d869b5c78 100644 --- a/npm_and_yarn/lib/dependabot/npm_and_yarn/file_updater/yarn_lockfile_updater.rb +++ b/npm_and_yarn/lib/dependabot/npm_and_yarn/file_updater/yarn_lockfile_updater.rb @@ -278,7 +278,8 @@ def handle_yarn_lock_updater_error(error, yarn_lock) error_message.include?(DEPENDENCY_MATCH_NOT_FOUND) unless resolvable_before_update?(yarn_lock) - error_handler.raise_resolvability_error(error_message, yarn_lock) + error_handler.raise_resolvability_error(error_message, + yarn_lock) end # Dependabot has probably messed something up with the update and we diff --git a/npm_and_yarn/spec/dependabot/npm_and_yarn/file_parser_spec.rb b/npm_and_yarn/spec/dependabot/npm_and_yarn/file_parser_spec.rb index 0794f0a60ae..afd7ca41fe6 100644 --- a/npm_and_yarn/spec/dependabot/npm_and_yarn/file_parser_spec.rb +++ b/npm_and_yarn/spec/dependabot/npm_and_yarn/file_parser_spec.rb @@ -1540,4 +1540,72 @@ end end end + + describe "missing package.json manifest file" do + let(:child_class) do + Class.new(described_class) do + def check_required_files + %w(manifest).each do |filename| + unless get_original_file(filename) + raise Dependabot::DependencyFileNotFound.new(nil, + "package.json not found.") + end + end + end + end + end + let(:parser_instance) do + child_class.new(dependency_files: files, source: source) + end + let(:source) do + Dependabot::Source.new( + provider: "github", + repo: "gocardless/bump", + directory: "/" + ) + end + + let(:gemfile) do + Dependabot::DependencyFile.new( + content: "a", + name: "manifest", + directory: "/path/to" + ) + end + let(:files) { [gemfile] } + + describe ".new" do + context "when the required file is present" do + let(:files) { [gemfile] } + + it "doesn't raise" do + expect { parser_instance }.not_to raise_error + end + end + + context "when the required file is missing" do + let(:files) { [] } + + it "raises" do + expect { parser_instance }.to raise_error(Dependabot::DependencyFileNotFound) + end + end + end + + describe "#get_original_file" do + subject { parser_instance.send(:get_original_file, filename) } + + context "when the requested file is present" do + let(:filename) { "manifest" } + + it { is_expected.to eq(gemfile) } + end + + context "when the requested file is not present" do + let(:filename) { "package.json" } + + it { is_expected.to be_nil } + end + end + end end diff --git a/npm_and_yarn/spec/dependabot/npm_and_yarn/file_updater_spec.rb b/npm_and_yarn/spec/dependabot/npm_and_yarn/file_updater_spec.rb index 9bd142dae6d..326d97cd8cb 100644 --- a/npm_and_yarn/spec/dependabot/npm_and_yarn/file_updater_spec.rb +++ b/npm_and_yarn/spec/dependabot/npm_and_yarn/file_updater_spec.rb @@ -4031,4 +4031,83 @@ end end end + + describe "without a package.json file" do + let(:child_class) do + Class.new(described_class) do + def check_required_files + %w(manifest).each do |filename| + unless get_original_file(filename) + raise Dependabot::DependencyFileNotFound.new(nil, + "package.json not found.") + end + end + end + end + end + let(:updater_instance) do + child_class.new( + dependency_files: files, + dependencies: [dependency], + credentials: [{ + "type" => "git_source", + "host" => "github.com" + }] + ) + end + + let(:manifest) do + Dependabot::DependencyFile.new( + content: "a", + name: "manifest", + directory: "/path/to" + ) + end + let(:dependency) do + Dependabot::Dependency.new( + name: "business", + version: "1.5.0", + package_manager: "bundler", + requirements: + [{ file: "manifest", requirement: "~> 1.4.0", groups: [], source: nil }] + ) + end + let(:files) { [manifest] } + + describe "new file updater" do + subject { -> { updater_instance } } + + context "when the required file is present" do + let(:files) { [manifest] } + + it "doesn't raise" do + expect { updater_instance }.not_to raise_error + end + end + + context "when the required file is missing" do + let(:files) { [] } + + it "raises" do + expect { updater_instance }.to raise_error(Dependabot::DependencyFileNotFound) + end + end + end + + describe "#get_original_file" do + subject { updater_instance.send(:get_original_file, filename) } + + context "when the requested file is present" do + let(:filename) { "manifest" } + + it { is_expected.to eq(manifest) } + end + + context "when the requested file is not present" do + let(:filename) { "package.json" } + + it { is_expected.to be_nil } + end + end + end end diff --git a/npm_and_yarn/spec/dependabot/npm_and_yarn/yarn_error_handler_spec.rb b/npm_and_yarn/spec/dependabot/npm_and_yarn/yarn_error_handler_spec.rb index c6276546fd8..6a5df08d0d1 100644 --- a/npm_and_yarn/spec/dependabot/npm_and_yarn/yarn_error_handler_spec.rb +++ b/npm_and_yarn/spec/dependabot/npm_and_yarn/yarn_error_handler_spec.rb @@ -341,6 +341,31 @@ end end + context "when the error message contains ESOCKETTIMEDOUT" do + let(:error_message) do + "https://registry.us.gympass.cloud/repository/npm-group/@gympass%2fmep-utils: ESOCKETTIMEDOUT" + end + + it "raises the corresponding error class with the correct message" do + expect { error_handler.handle_group_patterns(error, usage_error_message, { yarn_lock: yarn_lock }) } + .to raise_error(Dependabot::PrivateSourceTimedOut, "The following source timed out: " \ + "registry.us.gympass.cloud/repository/" \ + "npm-group/@gympass%2fmep-utils") + end + end + + context "when the error message contains socket hang up" do + let(:error_message) do + "https://registry.npm.taobao.org/vue-template-compiler: socket hang up" + end + + it "raises the corresponding error class with the correct message" do + expect { error_handler.handle_group_patterns(error, usage_error_message, { yarn_lock: yarn_lock }) } + .to raise_error(Dependabot::PrivateSourceTimedOut, "The following source timed out: " \ + "registry.npm.taobao.org/vue-template-compiler") + end + end + context "when the error message contains a recognized pattern in the error message" do it "raises the corresponding error class with the correct message" do expect { error_handler.handle_group_patterns(error, "", { yarn_lock: yarn_lock }) } diff --git a/updater/lib/dependabot/updater/operations/update_all_versions.rb b/updater/lib/dependabot/updater/operations/update_all_versions.rb index aed2f72605c..76b3b27a1a6 100644 --- a/updater/lib/dependabot/updater/operations/update_all_versions.rb +++ b/updater/lib/dependabot/updater/operations/update_all_versions.rb @@ -59,9 +59,8 @@ def dependencies def check_and_create_pr_with_error_handling(dependency) check_and_create_pull_request(dependency) rescue URI::InvalidURIError => e - msg = e.class.to_s + " with message: " + e.message - e = Dependabot::DependencyFileNotResolvable.new(msg) - error_handler.handle_dependency_error(error: e, dependency: dependency) + error_handler.handle_dependency_error(error: Dependabot::DependencyFileNotResolvable.new(e.message), + dependency: dependency) rescue Dependabot::InconsistentRegistryResponse => e error_handler.log_dependency_error( dependency: dependency, diff --git a/updater/spec/dependabot/updater_spec.rb b/updater/spec/dependabot/updater_spec.rb index d1151c44d7b..d3005d1192c 100644 --- a/updater/spec/dependabot/updater_spec.rb +++ b/updater/spec/dependabot/updater_spec.rb @@ -2028,6 +2028,44 @@ def expect_update_checker_with_ignored_versions(versions, dependency_matcher: an end end + context "when Dependabot::PrivateSourceAuthenticationFailure is raised with Unauthenticated message" do + it "doesn't report the error to the service" do + checker = stub_update_checker + error = Dependabot::PrivateSourceAuthenticationFailure.new("npm.fury.io") + values = [-> { raise error }, -> { true }, -> { true }, -> { true }] + allow(checker).to receive(:can_update?) { values.shift.call } + + job = build_job + service = build_service + updater = build_updater(service: service, job: job) + + expect(service).not_to receive(:capture_exception) + + updater.run + end + + it "tells the main backend" do + checker = stub_update_checker + error = Dependabot::PrivateSourceAuthenticationFailure.new("npm.fury.io") + values = [-> { raise error }, -> { true }, -> { true }, -> { true }] + allow(checker).to receive(:can_update?) { values.shift.call } + + job = build_job + service = build_service + updater = build_updater(service: service, job: job) + + expect(service) + .to receive(:record_update_job_error) + .with( + error_type: "private_source_authentication_failure", + error_details: { source: "npm.fury.io" }, + dependency: an_instance_of(Dependabot::Dependency) + ) + + updater.run + end + end + context "when Dependabot::GitDependenciesNotReachable is raised" do it "doesn't report the error to the service" do checker = stub_update_checker @@ -2066,6 +2104,44 @@ def expect_update_checker_with_ignored_versions(versions, dependency_matcher: an end end + context "when URI::InvalidURIError is raised" do + it "doesn't report the error to the service" do + checker = stub_update_checker + error = URI::InvalidURIError.new("https://registry.yarnpkg.com}/") + values = [-> { raise error }, -> { true }, -> { true }, -> { true }] + allow(checker).to receive(:can_update?) { values.shift.call } + + job = build_job + service = build_service + updater = build_updater(service: service, job: job) + + expect(service).not_to receive(:capture_exception) + + updater.run + end + + it "tells the main backend" do + checker = stub_update_checker + error = URI::InvalidURIError.new("https://registry.yarnpkg.com}/") + values = [-> { raise error }, -> { true }, -> { true }, -> { true }] + allow(checker).to receive(:can_update?) { values.shift.call } + + job = build_job + service = build_service + updater = build_updater(service: service, job: job) + + expect(service) + .to receive(:record_update_job_error) + .with( + error_type: "dependency_file_not_resolvable", + error_details: { message: "https://registry.yarnpkg.com}/" }, + dependency: an_instance_of(Dependabot::Dependency) + ) + + updater.run + end + end + context "when Dependabot::GitDependencyReferenceNotFound is raised" do it "doesn't report the error to the service" do checker = stub_update_checker