diff --git a/.rubocop.yml b/.rubocop.yml index 75991bfc..3a9503f0 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -341,6 +341,7 @@ Style/SpecialGlobalVars: Style/SelectByRegexp: Enabled: false Sorbet/TrueSigil: + Enabled: true Exclude: - "**/spec/**/*" Sorbet/StrictSigil: diff --git a/Rakefile b/Rakefile index 43dc8855..7bd302a5 100644 --- a/Rakefile +++ b/Rakefile @@ -7,34 +7,34 @@ require "uri" require "json" require "rubygems/package" require "bundler" -# require "./common/lib/dependabot" +require "./common/lib/dependabot" require "yaml" # ./dependabot-core.gemspec is purposefully excluded from this list # because it's an empty gem as a placeholder to prevent namesquatting. -# GEMSPECS = %w( -# common/dependabot-common.gemspec -# go_modules/dependabot-go_modules.gemspec -# terraform/dependabot-terraform.gemspec -# docker/dependabot-docker.gemspec -# git_submodules/dependabot-git_submodules.gemspec -# github_actions/dependabot-github_actions.gemspec -# nuget/dependabot-nuget.gemspec -# gradle/dependabot-gradle.gemspec -# maven/dependabot-maven.gemspec -# bundler/dependabot-bundler.gemspec -# elm/dependabot-elm.gemspec -# cargo/dependabot-cargo.gemspec -# npm_and_yarn/dependabot-npm_and_yarn.gemspec -# composer/dependabot-composer.gemspec -# hex/dependabot-hex.gemspec -# python/dependabot-python.gemspec -# pub/dependabot-pub.gemspec -# omnibus/dependabot-omnibus.gemspec -# silent/dependabot-silent.gemspec -# swift/dependabot-swift.gemspec -# devcontainers/dependabot-devcontainers.gemspec -# ).freeze +GEMSPECS = %w( + common/dependabot-common.gemspec + go_modules/dependabot-go_modules.gemspec + terraform/dependabot-terraform.gemspec + docker/dependabot-docker.gemspec + git_submodules/dependabot-git_submodules.gemspec + github_actions/dependabot-github_actions.gemspec + nuget/dependabot-nuget.gemspec + gradle/dependabot-gradle.gemspec + maven/dependabot-maven.gemspec + bundler/dependabot-bundler.gemspec + elm/dependabot-elm.gemspec + cargo/dependabot-cargo.gemspec + npm_and_yarn/dependabot-npm_and_yarn.gemspec + composer/dependabot-composer.gemspec + hex/dependabot-hex.gemspec + python/dependabot-python.gemspec + pub/dependabot-pub.gemspec + omnibus/dependabot-omnibus.gemspec + silent/dependabot-silent.gemspec + swift/dependabot-swift.gemspec + devcontainers/dependabot-devcontainers.gemspec +).freeze def run_command(command) puts "> #{command}" @@ -104,8 +104,8 @@ end namespace :rubocop do task :sort do File.write( - ".rubocop.yml", - YAML.load_file(".rubocop.yml").sort_by_key(true).to_yaml + "omnibus/.rubocop.yml", + YAML.load_file("omnibus/.rubocop.yml").sort_by_key(true).to_yaml ) end end diff --git a/update-files.ps1 b/update-files.ps1 index 179a7895..ce9330ad 100644 --- a/update-files.ps1 +++ b/update-files.ps1 @@ -14,7 +14,7 @@ $files = @( ".ruby-version" ".rubocop.yml" ".rubocop_todo.yml" - # "Rakefile" + "Rakefile" "updater/.rubocop.yml" # "updater/bin/fetch_files.rb" @@ -60,7 +60,6 @@ $files = @( "updater/spec/dependabot/sentry/exception_sanitizer_processor_spec.rb" "updater/spec/dependabot/sentry/sentry_context_processor_spec.rb" - # "updater/spec/dependabot/updater/operations/group_update_all_versions_spec.rb" # "updater/spec/dependabot/updater/operations/refresh_group_update_pull_request_spec.rb" "updater/spec/dependabot/updater/dependency_group_change_batch_spec.rb" "updater/spec/dependabot/updater/error_handler_spec.rb" @@ -74,7 +73,7 @@ $files = @( "updater/spec/dependabot/file_fetcher_command_spec.rb" "updater/spec/dependabot/job_spec.rb" "updater/spec/dependabot/service_spec.rb" - # "updater/spec/dependabot/update_files_command_spec.rb" + "updater/spec/dependabot/update_files_command_spec.rb" # "updater/spec/dependabot/updater_spec.rb" "updater/spec/fixtures/handle_error.json" @@ -153,7 +152,7 @@ $files = @( "updater/spec/support/dependency_file_helpers.rb" "updater/spec/support/dummy_pkg_helpers.rb" - # "updater/spec/spec_helper.rb" + "updater/spec/spec_helper.rb" ) # Download each file listed diff --git a/updater/bin/update_script.rb b/updater/bin/update_script.rb index 65172f2f..d6d8f7c5 100644 --- a/updater/bin/update_script.rb +++ b/updater/bin/update_script.rb @@ -1,3 +1,4 @@ +# typed: true # frozen_string_literal: true # rubocop:disable Style/GlobalVars diff --git a/updater/lib/tinglesoftware/dependabot/clients/azure.rb b/updater/lib/tinglesoftware/dependabot/clients/azure.rb index ae3550ab..6a5c0d24 100644 --- a/updater/lib/tinglesoftware/dependabot/clients/azure.rb +++ b/updater/lib/tinglesoftware/dependabot/clients/azure.rb @@ -1,4 +1,4 @@ -# typed: false +# typed: true # frozen_string_literal: true require "json" diff --git a/updater/lib/tinglesoftware/dependabot/vulnerabilities.rb b/updater/lib/tinglesoftware/dependabot/vulnerabilities.rb index f17d367b..9b422c74 100644 --- a/updater/lib/tinglesoftware/dependabot/vulnerabilities.rb +++ b/updater/lib/tinglesoftware/dependabot/vulnerabilities.rb @@ -1,4 +1,4 @@ -# typed: false +# typed: true # frozen_string_literal: true require "octokit" diff --git a/updater/spec/dependabot/update_files_command_spec.rb b/updater/spec/dependabot/update_files_command_spec.rb new file mode 100644 index 00000000..20a31a06 --- /dev/null +++ b/updater/spec/dependabot/update_files_command_spec.rb @@ -0,0 +1,395 @@ +# typed: false +# frozen_string_literal: true + +require "spec_helper" +require "dependabot/update_files_command" +require "tmpdir" + +RSpec.describe Dependabot::UpdateFilesCommand do + subject(:job) { described_class.new } + + let(:service) do + instance_double(Dependabot::Service, + capture_exception: nil, + mark_job_as_processed: nil, + record_update_job_error: nil, + record_update_job_unknown_error: nil, + update_dependency_list: nil, + increment_metric: nil, + wait_for_calls_to_finish: nil) + end + let(:job_definition) do + JSON.parse(fixture("file_fetcher_output/output.json")) + end + let(:job_id) { "123123" } + + before do + allow(Dependabot::Service).to receive(:new).and_return(service) + allow(Dependabot::Environment).to receive_messages(job_id: job_id, job_token: "mock_token", + job_definition: job_definition, repo_contents_path: nil) + end + + describe "#perform_job" do + subject(:perform_job) { job.perform_job } + + it "delegates to Dependabot::Updater" do + dummy_runner = double(run: nil) + base_commit_sha = "1c6331732c41e4557a16dacb82534f1d1c831848" + expect(Dependabot::Updater) + .to receive(:new) + .with( + service: service, + job: an_object_having_attributes(id: job_id, repo_contents_path: nil), + dependency_snapshot: an_object_having_attributes(base_commit_sha: base_commit_sha) + ) + .and_return(dummy_runner) + expect(dummy_runner).to receive(:run) + expect(service).to receive(:mark_job_as_processed) + .with(base_commit_sha) + + perform_job + end + + it "sends dependency metadata to the service" do + expect(service).to receive(:update_dependency_list) + .with(dependency_snapshot: an_instance_of(Dependabot::DependencySnapshot)) + + perform_job + end + + context "with vendoring_dependencies" do + let(:snapshot) do + instance_double(Dependabot::DependencySnapshot, + base_commit_sha: "1c6331732c41e4557a16dacb82534f1d1c831848") + end + let(:repo_contents_path) { "repo/path" } + + let(:job_definition) do + JSON.parse(fixture("file_fetcher_output/vendoring_output.json")) + end + + before do + allow(Dependabot::Environment).to receive(:repo_contents_path).and_return(repo_contents_path) + allow(Dependabot::DependencySnapshot).to receive(:create_from_job_definition).and_return(snapshot) + end + + it "delegates to Dependabot::Updater" do + dummy_runner = double(run: nil) + base_commit_sha = "1c6331732c41e4557a16dacb82534f1d1c831848" + expect(Dependabot::Updater) + .to receive(:new) + .with( + service: service, + job: an_object_having_attributes(id: job_id, repo_contents_path: repo_contents_path), + dependency_snapshot: snapshot + ) + .and_return(dummy_runner) + expect(dummy_runner).to receive(:run) + expect(service).to receive(:mark_job_as_processed) + .with(base_commit_sha) + + perform_job + end + end + end + + describe "#perform_job when there is an error parsing the dependency files" do + subject(:perform_job) { job.perform_job } + + before do + allow(Dependabot.logger).to receive(:info) + allow(Dependabot.logger).to receive(:error) + allow(Sentry).to receive(:capture_exception) + allow(Dependabot::DependencySnapshot).to receive(:create_from_job_definition).and_raise(error) + end + + shared_examples "a fast-failed job" do + it "marks the job as processed without proceeding further" do + expect(service).to receive(:mark_job_as_processed) + expect(Dependabot::Updater).not_to receive(:new) + + perform_job + end + end + + context "with an update files error (cloud)" do + let(:error) { StandardError.new("hell") } + + before do + Dependabot::Experiments.register(:record_update_job_unknown_error, true) + end + + after do + Dependabot::Experiments.reset! + end + + it_behaves_like "a fast-failed job" + + it "captures the exception and records to a update job error api" do + expect(service).to receive(:capture_exception) + expect(service).to receive(:record_update_job_error).with( + error_type: "update_files_error", + error_details: { + Dependabot::ErrorAttributes::BACKTRACE => an_instance_of(String), + Dependabot::ErrorAttributes::MESSAGE => "hell", + Dependabot::ErrorAttributes::CLASS => "StandardError", + Dependabot::ErrorAttributes::PACKAGE_MANAGER => "bundler", + Dependabot::ErrorAttributes::JOB_ID => "123123", + Dependabot::ErrorAttributes::DEPENDENCY_GROUPS => [] + } + ) + + perform_job + end + + it "captures the exception and records the a update job unknown error api" do + expect(service).to receive(:capture_exception) + expect(service).to receive(:record_update_job_unknown_error).with( + error_type: "update_files_error", + error_details: { + Dependabot::ErrorAttributes::BACKTRACE => an_instance_of(String), + Dependabot::ErrorAttributes::MESSAGE => "hell", + Dependabot::ErrorAttributes::CLASS => "StandardError", + Dependabot::ErrorAttributes::PACKAGE_MANAGER => "bundler", + Dependabot::ErrorAttributes::JOB_ID => "123123", + Dependabot::ErrorAttributes::DEPENDENCY_GROUPS => [] + } + ) + + perform_job + Dependabot::Experiments.reset! + end + end + + context "with an update files error (ghes)" do + let(:error) { StandardError.new("hell") } + + it_behaves_like "a fast-failed job" + + it "captures the exception and records to a update job error api" do + expect(service).to receive(:capture_exception) + expect(service).to receive(:record_update_job_error).with( + error_type: "update_files_error", + error_details: { + Dependabot::ErrorAttributes::BACKTRACE => an_instance_of(String), + Dependabot::ErrorAttributes::MESSAGE => "hell", + Dependabot::ErrorAttributes::CLASS => "StandardError", + Dependabot::ErrorAttributes::PACKAGE_MANAGER => "bundler", + Dependabot::ErrorAttributes::JOB_ID => "123123", + Dependabot::ErrorAttributes::DEPENDENCY_GROUPS => [] + } + ) + + perform_job + end + + it "captures the exception and does not records the update job unknown error api" do + expect(service).to receive(:capture_exception) + expect(service).not_to receive(:record_update_job_unknown_error) + + perform_job + Dependabot::Experiments.reset! + end + end + + context "with a Dependabot::RepoNotFound error" do + let(:error) { Dependabot::RepoNotFound.new("dependabot/four-oh-four") } + + it_behaves_like "a fast-failed job" + + it "does not capture the exception or record a job error" do + expect(service).not_to receive(:capture_exception) + expect(service).not_to receive(:record_update_job_error) + + perform_job + end + end + + context "with a Dependabot::DependencyFileNotEvaluatable error" do + let(:error) { Dependabot::DependencyFileNotEvaluatable.new("message") } + + it_behaves_like "a fast-failed job" + + it "only records a job error" do + expect(service).not_to receive(:capture_exception) + expect(service).to receive(:record_update_job_error).with( + error_type: "dependency_file_not_evaluatable", + error_details: { message: "message" } + ) + + perform_job + end + end + + context "with a Dependabot::DependencyFileNotResolvable error" do + let(:error) { Dependabot::DependencyFileNotResolvable.new("message") } + + it_behaves_like "a fast-failed job" + + it "only records a job error" do + expect(service).not_to receive(:capture_exception) + expect(service).to receive(:record_update_job_error).with( + error_type: "dependency_file_not_resolvable", + error_details: { message: "message" } + ) + + perform_job + end + end + + context "with a Dependabot::BranchNotFound error" do + let(:error) { Dependabot::BranchNotFound.new("my_branch") } + + it_behaves_like "a fast-failed job" + + it "only records a job error" do + expect(service).not_to receive(:capture_exception) + expect(service).to receive(:record_update_job_error).with( + error_type: "branch_not_found", + error_details: { "branch-name": "my_branch" } + ) + + perform_job + end + end + + context "with a Dependabot::DependencyFileNotParseable error" do + let(:error) { Dependabot::DependencyFileNotParseable.new("path/to/file", "a") } + + it_behaves_like "a fast-failed job" + + it "only records a job error" do + expect(service).not_to receive(:capture_exception) + expect(service).to receive(:record_update_job_error).with( + error_type: "dependency_file_not_parseable", + error_details: { "file-path": "path/to/file", message: "a" } + ) + + perform_job + end + end + + context "with a Dependabot::DependencyFileNotFound error" do + let(:error) { Dependabot::DependencyFileNotFound.new("path/to/file") } + + it_behaves_like "a fast-failed job" + + it "only records a job error" do + expect(service).not_to receive(:capture_exception) + expect(service).to receive(:record_update_job_error).with( + error_type: "dependency_file_not_found", + error_details: { + message: "path/to/file not found", + "file-path": "path/to/file" + } + ) + + perform_job + end + end + + context "with a Dependabot::PathDependenciesNotReachable error" do + let(:error) { Dependabot::PathDependenciesNotReachable.new(["bad_gem"]) } + + it_behaves_like "a fast-failed job" + + it "only records a job error" do + expect(service).not_to receive(:capture_exception) + expect(service).to receive(:record_update_job_error).with( + error_type: "path_dependencies_not_reachable", + error_details: { dependencies: ["bad_gem"] } + ) + + perform_job + end + end + + context "with a Dependabot::PrivateSourceAuthenticationFailure error" do + let(:error) { Dependabot::PrivateSourceAuthenticationFailure.new("some.example.com") } + + it_behaves_like "a fast-failed job" + + it "only records a job error" do + expect(service).not_to receive(:capture_exception) + expect(service).to receive(:record_update_job_error).with( + error_type: "private_source_authentication_failure", + error_details: { source: "some.example.com" } + ) + + perform_job + end + end + + context "with a Dependabot::GitDependenciesNotReachable error" do + let(:error) { Dependabot::GitDependenciesNotReachable.new("https://example.com") } + + it_behaves_like "a fast-failed job" + + it "only records a job error" do + expect(service).not_to receive(:capture_exception) + expect(service).to receive(:record_update_job_error).with( + error_type: "git_dependencies_not_reachable", + error_details: { "dependency-urls": ["https://example.com"] } + ) + + perform_job + end + end + + context "with a Dependabot::NotImplemented error" do + let(:error) { Dependabot::NotImplemented.new("foo") } + + it_behaves_like "a fast-failed job" + + it "only records a job error" do + expect(service).not_to receive(:capture_exception) + expect(service).to receive(:record_update_job_error).with( + error_type: "not_implemented", + error_details: { message: "foo" } + ) + + perform_job + end + end + + context "with Octokit::ServerError" do + let(:error) { Octokit::ServerError.new } + + it_behaves_like "a fast-failed job" + + it "only records a job error" do + expect(service).not_to receive(:capture_exception) + expect(service).to receive(:record_update_job_error).with( + error_type: "server_error", + error_details: nil + ) + + perform_job + end + end + + Dependabot::Updater::ErrorHandler::RUN_HALTING_ERRORS.each do |error_class, error_type| + context "with #{error_class}" do + let(:error) do + if error_class == Octokit::Unauthorized + Octokit::Unauthorized.new + else + error_class.new("message") + end + end + + it_behaves_like "a fast-failed job" + + it "only records a job error" do + expect(service).not_to receive(:capture_exception) + expect(service).to receive(:record_update_job_error).with( + error_type: error_type, + error_details: nil + ) + + perform_job + end + end + end + end +end