Skip to content

Commit

Permalink
Fixes RuntimeError-No package.json issue (#10392)
Browse files Browse the repository at this point in the history
* Fixes RuntimeError-No package.json issue
  • Loading branch information
sachin-sandhu authored Aug 9, 2024
1 parent 4aac28c commit 1217a01
Show file tree
Hide file tree
Showing 10 changed files with 285 additions and 7 deletions.
29 changes: 28 additions & 1 deletion npm_and_yarn/lib/dependabot/npm_and_yarn.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ module NpmAndYarn
# Used to check if error message contains timeout fetching package
TIMEOUT_FETCHING_PACKAGE_REGEX = %r{(?<url>.+)/(?<package>[^/]+): ETIMEDOUT}

ESOCKETTIMEDOUT = /(?<package>.*?): ESOCKETTIMEDOUT/

SOCKET_HANG_UP = /(?<url>.*?): socket hang up/

# Used to identify git unreachable error
UNREACHABLE_GIT_CHECK_REGEX = /ls-remote --tags --heads (?<url>.*)/

Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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)
},
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion npm_and_yarn/lib/dependabot/npm_and_yarn/file_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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) }
Expand Down
2 changes: 1 addition & 1 deletion npm_and_yarn/lib/dependabot/npm_and_yarn/file_updater.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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]) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ def updated_lockfile_reponse(response)
ERROR_E401 = /code E401/
ERROR_E403 = /code E403/
ERROR_EAI_AGAIN = /request to (?<url>.*) failed, reason: getaddrinfo EAI_AGAIN/
PACKAGE_DISCOVERY_FAIL = /Couldn't find package "(?<pkg>.*)" *.* on the "(?<regis>.*)" registry./

# TODO: look into fixing this in npm, seems like a bug in the git
# downloader introduced in npm 7
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
68 changes: 68 additions & 0 deletions npm_and_yarn/spec/dependabot/npm_and_yarn/file_parser_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
79 changes: 79 additions & 0 deletions npm_and_yarn/spec/dependabot/npm_and_yarn/file_updater_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
76 changes: 76 additions & 0 deletions updater/spec/dependabot/updater_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 1217a01

Please sign in to comment.