# typed: false # frozen_string_literal: true require "spec_helper" require "shared_contexts" require "dependabot/bundler/update_checker" require "dependabot/dependency_file" require "dependabot/dependency" require "dependabot/requirements_update_strategy" require_common_spec "update_checkers/shared_examples_for_update_checkers" RSpec.describe Dependabot::Bundler::UpdateChecker do let(:rubygems_url) { "https://rubygems.org/api/v1/" } let(:requirements) do [{ file: "Gemfile", requirement: "~> 1.4.0", groups: [], source: nil }] end let(:current_version) { "1.4.0" } let(:dependency_name) { "business" } let(:dependency) do Dependabot::Dependency.new( name: dependency_name, version: current_version, requirements: requirements, package_manager: "bundler" ) end let(:requirements_update_strategy) { nil } let(:security_advisories) { [] } let(:ignored_versions) { [] } let(:directory) { "/" } let(:github_token) { "token" } let(:dependency_files) { bundler_project_dependency_files("gemfile") } let(:update_cooldown) { nil } let(:credentials) do [{ "type" => "git_source", "host" => "github.com", "username" => "x-access-token", "password" => "token" }] end let(:checker) do described_class.new( dependency: dependency, dependency_files: dependency_files, credentials: credentials, ignored_versions: ignored_versions, security_advisories: security_advisories, update_cooldown: update_cooldown, requirements_update_strategy: requirements_update_strategy ) end before do stub_request(:get, /.*/).to_return(status: 404) end it_behaves_like "an update checker" describe "#latest_version" do subject { checker.latest_version } context "with a rubygems source" do before do rubygems_response = fixture("ruby", "rubygems_response_versions.json") stub_request(:get, rubygems_url + "versions/business.json") .to_return(status: 200, body: rubygems_response) end it { is_expected.to eq(Dependabot::Bundler::Version.new("1.5.0")) } context "when that only appears in the lockfile" do let(:dependency_files) { bundler_project_dependency_files("subdependency") } let(:requirements) { [] } let(:dependency_name) { "i18n" } let(:current_version) { "0.7.0.beta1" } before do rubygems_response = fixture("ruby", "rubygems_response_versions.json") stub_request(:get, rubygems_url + "versions/i18n.json") .to_return(status: 200, body: rubygems_response) end it { is_expected.to eq(Dependabot::Bundler::Version.new("1.6.0.beta")) } end context "when the gem isn't on Rubygems" do before do stub_request(:get, rubygems_url + "versions/business.json") .to_return(status: 404, body: "This rubygem could not be found.") end it { is_expected.to be_nil } end context "with a Gemfile that includes a file with require_relative" do let(:dependency_files) { bundler_project_dependency_files("includes_require_relative_gemfile") } let(:directory) { "app/" } it { is_expected.to eq(Dependabot::Bundler::Version.new("1.5.0")) } end context "with a gem.rb and gems.locked setup" do let(:dependency_files) { bundler_project_dependency_files("gems_rb") } let(:requirements) do [{ file: "gems.rb", requirement: "~> 1.4.0", groups: [], source: nil }] end it { is_expected.to eq(Dependabot::Bundler::Version.new("1.5.0")) } end end context "with extra nonrelevant credentials" do before do rubygems_response = fixture("ruby", "rubygems_response_versions.json") stub_request(:get, rubygems_url + "versions/business.json") .to_return(status: 200, body: rubygems_response) end let(:credentials) do [{ "type" => "git_source", "host" => "github.com", "username" => "x-access-token", "password" => "token" }, { "type" => "npm_registry", "registry" => "npm.fury.io/dependabot", "token" => "secret_token" }] end it { is_expected.to eq(Dependabot::Bundler::Version.new("1.5.0")) } end context "with a private rubygems source" do let(:dependency_files) { bundler_project_dependency_files("specified_source") } let(:requirements) do [{ file: "Gemfile", requirement: ">= 0", groups: [], source: { type: "rubygems" } }] end let(:registry_url) { "https://repo.fury.io/greysteil/" } let(:gemfury_business_url) do "https://repo.fury.io/greysteil/api/v1/dependencies?gems=business" end before do bundler_version = PackageManagerHelper.bundler_version # We only need to stub out the version callout since it would # otherwise call out to the internet in a shell command allow(Dependabot::Bundler::NativeHelpers) .to receive(:run_bundler_subprocess) .with({ bundler_version: bundler_version, function: "dependency_source_type", options: anything, args: anything }).and_return("private") allow(Dependabot::Bundler::NativeHelpers) .to receive(:run_bundler_subprocess) .with({ bundler_version: bundler_version, function: "private_registry_versions", options: anything, args: anything }) .and_return( ["1.5.0", "1.9.0", "1.10.0.beta"] ) rubygems_response = fixture("ruby", "rubygems_response_versions.json") stub_request(:get, rubygems_url + "versions/business.json") .to_return(status: 200, body: rubygems_response) end it { is_expected.to eq(Dependabot::Bundler::Version.new("1.5.0")) } end context "when given a git source" do let(:dependency_files) { bundler_project_dependency_files("git_source_no_ref") } before do rubygems_response = fixture("ruby", "rubygems_response_versions.json") stub_request(:get, rubygems_url + "versions/business.json") .to_return(status: 200, body: rubygems_response) stub_request(:get, "https://github.com/dependabot-fixtures/business/api/v1/versions/business.json") .to_return(status: 200, body: rubygems_response) end context "when that is the gem we're checking for" do let(:dependency_name) { "business" } let(:current_version) { "a1b78a929dac93a52f08db4f2847d76d6cfe39bd" } let(:requirements) do [{ file: "Gemfile", requirement: ">= 0", groups: [], source: { type: "git", url: "https://github.com/dependabot-fixtures/business", branch: "master", ref: "master" } }] end context "when head of the gem's branch is included in a release" do before do allow_any_instance_of(Dependabot::GitCommitChecker) .to receive(:branch_or_ref_in_release?) .and_return(true) end it { is_expected.to eq("799cd60c8cd51c9f379b423d506fc02c3a68e895") } end context "when head of the gem's branch is not included in a release" do before do allow_any_instance_of(Dependabot::GitCommitChecker) .to receive(:branch_or_ref_in_release?) .and_return(false) git_url = "https://github.com/dependabot-fixtures/business.git" git_header = { "content-type" => "application/x-git-upload-pack-advertisement" } stub_request(:get, git_url + "/info/refs?service=git-upload-pack") .to_return( status: 200, body: fixture("git", "upload_packs", "business"), headers: git_header ) end it "fetches the latest SHA-1 hash" do expect(checker.latest_version) .to eq("7bb4e41ce5164074a0920d5b5770d196b4d90104") end end context "when the gem's tag is pinned" do let(:dependency_files) { bundler_project_dependency_files("git_source") } let(:requirements) do [{ file: "Gemfile", requirement: ">= 0", groups: [], source: { type: "git", url: "https://github.com/dependabot-fixtures/business", branch: "master", ref: "a1b78a9" } }] end context "when the gem isn't on Rubygems" do before do stub_request(:get, rubygems_url + "gems/business.json") .to_return(status: 404, body: "This rubygem could not be found.") end it { is_expected.to eq(current_version) } end context "when the reference isn't included in the new version" do before do allow_any_instance_of(Dependabot::GitCommitChecker) .to receive(:branch_or_ref_in_release?) .and_return(false) end it "respects the pin" do expect(checker.latest_version).to eq(current_version) expect(checker.can_update?(requirements_to_unlock: :own)) .to be(false) end end context "when the reference is included in the new version" do before do allow_any_instance_of(Dependabot::GitCommitChecker) .to receive(:branch_or_ref_in_release?) .and_return(true) end it { is_expected.to eq("a1b78a929dac93a52f08db4f2847d76d6cfe39bd") } end context "when the pin looks like a version" do let(:requirements) do [{ file: "Gemfile", requirement: ">= 0", groups: [], source: { type: "git", url: "https://github.com/dependabot-fixtures/business", branch: "master", ref: "v1.0.0" } }] end let(:upload_pack_fixture) { "business" } before do stub_request(:get, rubygems_url + "gems/business.json") .to_return(status: 404, body: "This rubygem could not be found.") url = "https://github.com/dependabot-fixtures/business.git" git_header = { "content-type" => "application/x-git-upload-pack-advertisement" } stub_request(:get, url + "/info/refs?service=git-upload-pack") .with(basic_auth: %w(x-access-token token)) .to_return( status: 200, body: fixture("git", "upload_packs", upload_pack_fixture), headers: git_header ) end it "fetches the latest SHA-1 hash of the latest version tag" do expect(checker.latest_version) .to eq("37f41032a0f191507903ebbae8a5c0cb945d7585") end context "when there are no tags" do let(:upload_pack_fixture) { "no_tags" } it "returns the current version" do expect(checker.latest_version).to eq(current_version) end end end end end end context "when given a path source" do let(:dependency_files) { bundler_project_dependency_files("path_source") } before do rubygems_response = fixture("ruby", "rubygems_response_versions.json") stub_request(:get, rubygems_url + "versions/example.json") .to_return(status: 200, body: rubygems_response) end context "with a downloaded gemspec" do context "when that is the gem we're checking" do let(:dependency_name) { "example" } let(:current_version) { "0.9.3" } let(:requirements) do [{ file: "Gemfile", requirement: ">= 0", groups: [], source: { type: "path" } }] end it { is_expected.to be_nil } end end end describe "with cooldown options" do let(:update_cooldown) do Dependabot::Package::ReleaseCooldownOptions.new(default_days: 7) end let(:expected_cooldown_options) do Dependabot::Package::ReleaseCooldownOptions.new( default_days: 7, semver_major_days: 7, semver_minor_days: 7, semver_patch_days: 7, include: [], exclude: [] ) end before do # Mock the LatestVersionFinder to verify it receives cooldown_options latest_version_finder = instance_double(Dependabot::Bundler::UpdateChecker::LatestVersionFinder) allow(latest_version_finder) .to receive(:latest_version_details).and_return({ version: Dependabot::Bundler::Version.new("1.5.0") }) allow(Dependabot::Bundler::UpdateChecker::LatestVersionFinder) .to receive(:new).and_return(latest_version_finder) end it "passes cooldown_options to LatestVersionFinder" do checker.latest_version expect(Dependabot::Bundler::UpdateChecker::LatestVersionFinder).to have_received(:new).with( hash_including( cooldown_options: an_object_having_attributes( default_days: expected_cooldown_options.default_days, semver_major_days: expected_cooldown_options.semver_major_days, semver_minor_days: expected_cooldown_options.semver_minor_days, semver_patch_days: expected_cooldown_options.semver_patch_days, include: expected_cooldown_options.include, exclude: expected_cooldown_options.exclude ) ) ) end end end describe "#lowest_security_fix_version" do subject(:lowest_security_fix_version) { checker.lowest_security_fix_version } context "with a rubygems source" do let(:current_version) { "1.2.0" } let(:requirements) do [{ file: "Gemfile", requirement: "~> 1.2.0", groups: [], source: nil }] end before do rubygems_response = fixture("ruby", "rubygems_response_versions.json") stub_request(:get, rubygems_url + "versions/business.json") .to_return(status: 200, body: rubygems_response) end it "finds the lowest available non-vulnerable version" do expect(lowest_security_fix_version).to eq(Dependabot::Bundler::Version.new("1.3.0")) end context "with a security vulnerability" do let(:security_advisories) do [ Dependabot::SecurityAdvisory.new( dependency_name: dependency_name, package_manager: "bundler", vulnerable_versions: ["<= 1.3.0"] ) ] end it "finds the lowest available non-vulnerable version" do expect(lowest_security_fix_version).to eq(Dependabot::Bundler::Version.new("1.4.0")) end end end end describe "#latest_version_resolvable_with_full_unlock?" do subject { checker.send(:latest_version_resolvable_with_full_unlock?) } include_context "when stubbing rubygems compact index" context "with no latest version" do before do stub_request(:get, rubygems_url + "versions/business.json") .to_return(status: 404, body: "This rubygem could not be found.") end it { is_expected.to be_falsey } end context "with a latest version" do before do allow(checker).to receive(:latest_version).and_return(Dependabot::Bundler::Version.new(target_version)) end context "when the force updater raises" do let(:dependency_files) { bundler_project_dependency_files("subdep_blocked_by_subdep") } let(:target_version) { "2.0.0" } let(:dependency_name) { "dummy-pkg-a" } let(:requirements) do [{ file: "Gemfile", requirement: "~> 1.0.0", groups: [], source: nil }] end it { is_expected.to be_falsey } end context "when the force updater succeeds" do let(:dependency_files) { bundler_project_dependency_files("version_conflict") } let(:target_version) { "3.6.0" } let(:dependency_name) { "rspec-mocks" } let(:requirements) do [{ file: "Gemfile", requirement: "3.5.0", groups: [], source: nil }] end it { is_expected.to be_truthy } end end end describe "#updated_dependencies_after_full_unlock" do subject(:updated_dependencies_after_full_unlock) do checker.send(:updated_dependencies_after_full_unlock) end include_context "when stubbing rubygems compact index" context "with a latest version" do before do allow(checker).to receive(:latest_version).and_return(Dependabot::Bundler::Version.new(target_version)) end context "when the force updater succeeds" do let(:dependency_files) { bundler_project_dependency_files("version_conflict") } let(:target_version) { "3.6.0" } let(:dependency_name) { "rspec-mocks" } let(:requirements) do [{ file: "Gemfile", requirement: "3.5.0", groups: [:default], source: nil }] end let(:expected_requirements) do [{ file: "Gemfile", requirement: "3.6.0", groups: [:default], source: nil }] end it "returns the right array of updated dependencies" do expect(updated_dependencies_after_full_unlock).to contain_exactly( Dependabot::Dependency.new( name: "rspec-mocks", version: "3.6.0", previous_version: "3.5.0", requirements: expected_requirements, previous_requirements: requirements, package_manager: "bundler" ), Dependabot::Dependency.new( name: "rspec-support", version: "3.6.0", previous_version: "3.5.0", requirements: expected_requirements, previous_requirements: requirements, package_manager: "bundler" ) ) end context "with a gem.rb and gems.locked setup" do let(:dependency_files) { bundler_project_dependency_files("version_conflict_gems_rb") } let(:requirements) do [{ file: "gems.rb", requirement: "3.5.0", groups: [:default], source: nil }] end let(:expected_requirements) do [{ file: "gems.rb", requirement: "3.6.0", groups: [:default], source: nil }] end it "returns the right array of updated dependencies" do expect(updated_dependencies_after_full_unlock).to contain_exactly( Dependabot::Dependency.new( name: "rspec-mocks", version: "3.6.0", previous_version: "3.5.0", requirements: expected_requirements, previous_requirements: requirements, package_manager: "bundler" ), Dependabot::Dependency.new( name: "rspec-support", version: "3.6.0", previous_version: "3.5.0", requirements: expected_requirements, previous_requirements: requirements, package_manager: "bundler" ) ) end end end end end describe "#conflicting_dependencies" do subject(:conflicting_dependencies) { checker.conflicting_dependencies } include_context "when stubbing rubygems compact index" include_context "when stubbing rubygems versions api" let(:dependency_files) { bundler_project_dependency_files("subdep_blocked_by_subdep") } let(:target_version) { "2.0.0" } let(:dependency_name) { "dummy-pkg-a" } let(:requirements) do [{ file: "Gemfile", requirement: "~> 1.0.0", groups: [], source: nil }] end let(:requirements) { [] } let(:security_advisories) do [ Dependabot::SecurityAdvisory.new( dependency_name: dependency_name, package_manager: "bundler", vulnerable_versions: ["< 2.0.0"] ) ] end before do allow(checker) .to receive(:lowest_security_fix_version) .and_return(target_version) end it do expect(conflicting_dependencies).to eq( [{ "explanation" => "dummy-pkg-b (1.0.0) requires dummy-pkg-a (< 2.0.0)", "name" => "dummy-pkg-b", "version" => "1.0.0", "requirement" => "< 2.0.0" }] ) end context "when lowest_security_fix_version returns a Version object" do let(:version_object) { Dependabot::Bundler::Version.new("2.0.0") } let(:mock_resolver) { instance_double(Dependabot::Bundler::UpdateChecker::ConflictingDependencyResolver) } before do allow(checker) .to receive(:lowest_security_fix_version) .and_return(version_object) # Mock the ConflictingDependencyResolver constructor to return our mock allow(Dependabot::Bundler::UpdateChecker::ConflictingDependencyResolver) .to receive(:new) .and_return(mock_resolver) # Set up the mock to expect the string version and return a result allow(mock_resolver) .to receive(:conflicting_dependencies) .with( dependency: dependency, target_version: "2.0.0" # Should be a string, not Version object ) .and_return([ { "explanation" => "dummy-pkg-b (1.0.0) requires dummy-pkg-a (< 2.0.0)", "name" => "dummy-pkg-b", "version" => "1.0.0", "requirement" => "< 2.0.0" } ]) end it "converts the Version object to string before passing to ConflictingDependencyResolver" do result = conflicting_dependencies expect(result).to eq( [ { "explanation" => "dummy-pkg-b (1.0.0) requires dummy-pkg-a (< 2.0.0)", "name" => "dummy-pkg-b", "version" => "1.0.0", "requirement" => "< 2.0.0" } ] ) # Verify that the ConflictingDependencyResolver was called with a string target_version expect(mock_resolver) .to have_received(:conflicting_dependencies) .with( dependency: dependency, target_version: "2.0.0" ) end it "does not raise a Sorbet type error when converting Version to String" do expect { conflicting_dependencies }.not_to raise_error end it "preserves the version semantics when converting to string" do conflicting_dependencies # Verify the exact string representation matches what .to_s produces expect(mock_resolver) .to have_received(:conflicting_dependencies) .with( dependency: dependency, target_version: version_object.to_s ) end end end describe "#latest_resolvable_version" do subject(:latest_resolvable_version) { checker.latest_resolvable_version } include_context "when stubbing rubygems compact index" include_context "when stubbing rubygems versions api" context "when given a gem from rubygems" do context "when that only appears in the lockfile" do let(:dependency_files) { bundler_project_dependency_files("subdependency") } let(:requirements) { [] } let(:dependency_name) { "i18n" } let(:current_version) { "0.7.0.beta1" } it { is_expected.to eq(Dependabot::Bundler::Version.new("0.7.0")) } end context "with no version specified" do let(:dependency_files) { bundler_project_dependency_files("version_not_specified") } let(:requirements) do [{ file: "Gemfile", requirement: ">= 0", groups: [], source: nil }] end it { is_expected.to eq(Dependabot::Bundler::Version.new("1.13.0")) } context "when the user is ignoring the latest version" do let(:ignored_versions) { [">= 1.7.0.a, < 2.0"] } it { is_expected.to eq(Dependabot::Bundler::Version.new("1.6.0")) } end end context "with a greater than or equal to matcher" do let(:dependency_files) { bundler_project_dependency_files("gte_matcher") } let(:requirements) do [{ file: "Gemfile", requirement: ">= 1.4.0", groups: [], source: nil }] end it { is_expected.to eq(Dependabot::Bundler::Version.new("1.13.0")) } end context "with multiple requirements" do let(:dependency_files) { bundler_project_dependency_files("version_between_bounds_gemfile") } let(:requirements) do [{ file: "Gemfile", requirement: "> 1.0.0, < 1.5.0", groups: [], source: nil }] end it { is_expected.to eq(Dependabot::Bundler::Version.new("1.13.0")) } end context "with a gem.rb and gems.locked setup" do context "when that only appears in the lockfile" do let(:dependency_files) { bundler_project_dependency_files("subdependency_gems_rb") } let(:requirements) { [] } let(:dependency_name) { "i18n" } let(:current_version) { "0.7.0.beta1" } it { is_expected.to eq(Dependabot::Bundler::Version.new("0.7.0")) } end context "with a range requirement" do let(:dependency_files) { bundler_project_dependency_files("version_between_bounds_gems_rb") } let(:requirements) do [{ file: "gems.rb", requirement: "> 1.0.0, < 1.5.0", groups: [], source: nil }] end it { is_expected.to eq(Dependabot::Bundler::Version.new("1.13.0")) } end end end context "when given a gem with a path source" do context "with a downloaded gemspec" do let(:dependency_files) { bundler_project_dependency_files("path_source_no_overlap") } it { is_expected.to eq(Dependabot::Bundler::Version.new("1.13.0")) } it "doesn't persist any temporary changes to Bundler's root" do expect { checker.latest_resolvable_version } .not_to(change(::Bundler, :root)) end context "when that requires other files" do let(:dependency_files) { bundler_project_dependency_files("path_source_no_overlap_with_require") } it { is_expected.to eq(Dependabot::Bundler::Version.new("1.13.0")) } end context "when that is the gem we're checking" do before do rubygems_response = fixture("ruby", "rubygems_response_versions.json") stub_request(:get, rubygems_url + "versions/example.json").to_return(status: 200, body: rubygems_response) end let(:dependency_name) { "example" } let(:current_version) { "0.9.3" } it { is_expected.to eq(Dependabot::Bundler::Version.new("0.9.3")) } end context "when that has a .specification" do let(:dependency_files) { bundler_project_dependency_files("path_source_statesman") } it { is_expected.to eq(Dependabot::Bundler::Version.new("1.13.0")) } end end end context "when given a gem with a git source" do let(:dependency_files) { bundler_project_dependency_files("git_source_no_ref") } context "when the gem under consideration is the one we're checking" do let(:dependency_name) { "business" } let(:current_version) { "cff701b3bfb182afc99a85657d7c9f3d6c1ccce2" } let(:requirements) do [{ file: "Gemfile", requirement: ">= 0", groups: [], source: { type: "git", url: "https://github.com/dependabot-fixtures/business", branch: "master", ref: "master" } }] end context "when the head of the branch isn't released" do before do allow_any_instance_of(Dependabot::GitCommitChecker) .to receive(:branch_or_ref_in_release?) .and_return(false) git_url = "https://github.com/dependabot-fixtures/business.git" git_header = { "content-type" => "application/x-git-upload-pack-advertisement" } stub_request(:get, git_url + "/info/refs?service=git-upload-pack") .to_return( status: 200, body: fixture("git", "upload_packs", "business"), headers: git_header ) end it "fetches the latest SHA-1 hash" do version = checker.latest_resolvable_version expect(version).to match(/^[0-9a-f]{40}$/) expect(version).not_to eq(current_version) end context "when the Gemfile doesn't specify a git source" do let(:dependency_files) { bundler_project_dependency_files("git_source_mismatched") } # If the dependency has a git version in the Gemfile.lock but not in # the Gemfile (i.e., because they're out-of-sync) we leave that # problem to the user. it { is_expected.to be_nil } end end context "when the head of the branch is released" do before do allow_any_instance_of(Dependabot::GitCommitChecker) .to receive(:branch_or_ref_in_release?) .and_return(true) end it { is_expected.to eq("799cd60c8cd51c9f379b423d506fc02c3a68e895") } end context "when the dependency has never been released" do let(:dependency_files) { bundler_project_dependency_files("git_source") } let(:current_version) { "cff701b3bfb182afc99a85657d7c9f3d6c1ccce2" } let(:requirements) do [{ file: "Gemfile", requirement: ">= 0", groups: [], source: { type: "git", url: "https://github.com/dependabot-fixtures/prius", branch: "master", ref: "master" } }] end before do allow_any_instance_of(Dependabot::GitCommitChecker) .to receive(:branch_or_ref_in_release?) .and_return(false) git_url = "https://github.com/dependabot-fixtures/prius.git" git_header = { "content-type" => "application/x-git-upload-pack-advertisement" } stub_request(:get, git_url + "/info/refs?service=git-upload-pack") .to_return( status: 200, body: fixture("git", "upload_packs", "prius"), headers: git_header ) end it "fetches the latest SHA-1 hash" do version = checker.latest_resolvable_version expect(version).to match(/^[0-9a-f]{40}$/) expect(version).not_to eq(current_version) end end context "when the gem's tag is pinned" do let(:dependency_files) { bundler_project_dependency_files("git_source") } let(:dependency_name) { "business" } let(:current_version) { "a1b78a929dac93a52f08db4f2847d76d6cfe39bd" } let(:requirements) do [{ file: "Gemfile", requirement: ">= 0", groups: [], source: { type: "git", url: "https://github.com/dependabot-fixtures/business", branch: "master", ref: "a1b78a9" } }] end context "when the reference isn't included in the new version" do before do allow_any_instance_of(Dependabot::GitCommitChecker) .to receive(:branch_or_ref_in_release?) .and_return(false) end it "respects the pin" do expect(checker.latest_resolvable_version) .to eq("a1b78a929dac93a52f08db4f2847d76d6cfe39bd") expect(checker.can_update?(requirements_to_unlock: :own)) .to be(false) end end context "when the reference is included in the new version" do before do allow_any_instance_of(Dependabot::GitCommitChecker) .to receive(:branch_or_ref_in_release?) .and_return(true) end it { is_expected.to eq("a1b78a929dac93a52f08db4f2847d76d6cfe39bd") } end context "when the release appears to be a version" do let(:requirements) do [{ file: "Gemfile", requirement: ">= 0", groups: [], source: { type: "git", url: "https://github.com/dependabot-fixtures/business", branch: "master", ref: "v1.0.0" } }] end let(:upload_pack_fixture) { "business" } before do stub_request(:get, rubygems_url + "gems/business.json") .to_return(status: 404, body: "This rubygem could not be found.") url = "https://github.com/dependabot-fixtures/business.git" git_header = { "content-type" => "application/x-git-upload-pack-advertisement" } stub_request(:get, url + "/info/refs?service=git-upload-pack") .with(basic_auth: %w(x-access-token token)) .to_return( status: 200, body: fixture("git", "upload_packs", upload_pack_fixture), headers: git_header ) end it "fetches the latest SHA-1 hash of the latest version tag" do expect(checker.latest_resolvable_version) .to eq("37f41032a0f191507903ebbae8a5c0cb945d7585") end context "when the dependency has never been released" do let(:dependency_files) { bundler_project_dependency_files("git_source_unreleased") } let(:dependency_name) { "dummy-git-dependency" } let(:current_version) do "20151f9b67c8a04461fa0ee28385b6187b86587b" end let(:upload_pack_fixture) { "dummy-git-dependency" } let(:requirements) do [{ file: "Gemfile", requirement: ">= 0", groups: [], source: { type: "git", url: "https://github.com/dependabot-fixtures/" \ "ruby-dummy-git-dependency", branch: nil, ref: "v1.0.0" } }] end before do stub_request( :get, rubygems_url + "gems/dummy-git-dependency.json" ).to_return(status: 404) url = "https://github.com/dependabot-fixtures/" \ "ruby-dummy-git-dependency.git" git_header = { "content-type" => "application/x-git-upload-pack-advertisement" } stub_request(:get, url + "/info/refs?service=git-upload-pack") .with(basic_auth: %w(x-access-token token)) .to_return( status: 200, body: fixture("git", "upload_packs", upload_pack_fixture), headers: git_header ) end it "returns the commit SHA for the updated version" do expect(checker.latest_resolvable_version) .to eq("c0e25c2eb332122873f73acb3b61fb2e261cfd8f") end end context "when there are no tags" do let(:upload_pack_fixture) { "no_tags" } it "returns the current version" do expect(checker.latest_resolvable_version).to eq(current_version) end end context "when updating the gem results in a conflict" do let(:dependency_files) { bundler_project_dependency_files("git_source_with_tag_conflict") } let(:dependency_name) { "onfido" } let(:current_version) do "7b36eac82a7e42049052a58af0a7943fe0363714" end let(:requirements) do [{ file: "Gemfile", requirement: ">= 0", groups: [], source: { type: "git", url: "https://github.com/hvssle/onfido", branch: "master", ref: "v0.4.0" } }] end before do allow_any_instance_of(Dependabot::GitCommitChecker) .to receive(:branch_or_ref_in_release?) .and_return(false) refs_url = "https://github.com/hvssle/onfido.git/info/refs" git_header = { "content-type" => "application/x-git-upload-pack-advertisement" } stub_request(:get, refs_url + "?service=git-upload-pack") .to_return( status: 200, body: fixture("git", "upload_packs", "onfido"), headers: git_header ) end it { is_expected.to eq(dependency.version) } end end end context "when the gem has a version specified, too" do let(:dependency_files) { bundler_project_dependency_files("git_source_with_version_gemfile") } let(:requirements) do [{ file: "Gemfile", requirement: "~> 1.0.0", groups: [], source: { type: "git", url: "https://github.com/dependabot-fixtures/" \ "dependabot-test-ruby-package", branch: "master", ref: "master" } }] end let(:dependency_name) { "dependabot-test-ruby-package" } let(:current_version) { "81073f9462f228c6894e3e384d0718def310d99f" } before do allow_any_instance_of(Dependabot::GitCommitChecker) .to receive(:branch_or_ref_in_release?) .and_return(false) stub_request( :get, rubygems_url + "versions/dependabot-test-ruby-package.json" ).to_return(status: 404) git_url = "https://github.com/dependabot-fixtures/" \ "dependabot-test-ruby-package.git" git_header = { "content-type" => "application/x-git-upload-pack-advertisement" } stub_request(:get, git_url + "/info/refs?service=git-upload-pack") .to_return( status: 200, body: fixture( "git", "upload_packs", "dependabot-test-ruby-package" ), headers: git_header ) end it "fetches the latest SHA-1 hash" do version = checker.latest_resolvable_version expect(version).to match(/^[0-9a-f]{40}$/) expect(version).not_to eq "c5bf1bd47935504072ac0eba1006cf4d67af6a7a" end end context "when the gem has a bad branch" do let(:dependency_files) { bundler_project_dependency_files("bad_branch") } let(:dependency_name) { "prius" } let(:current_version) { "2.0.0" } let(:requirements) do [{ file: "Gemfile", requirement: "~> 1.0.0", groups: [], source: { type: "git", url: "https://github.com/dependabot-fixtures/prius", branch: "master", ref: "master" } }] end before do allow_any_instance_of(Dependabot::GitCommitChecker) .to receive(:branch_or_ref_in_release?) .and_return(false) git_url = "https://github.com/dependabot-fixtures/prius.git" git_header = { "content-type" => "application/x-git-upload-pack-advertisement" } stub_request(:get, git_url + "/info/refs?service=git-upload-pack") .to_return( status: 200, body: fixture("git", "upload_packs", "prius"), headers: git_header ) allow(checker) .to receive(:latest_resolvable_version_details) .and_call_original allow(checker) .to receive(:latest_resolvable_version_details) .with(remove_git_source: true) .and_return(version: Dependabot::Bundler::Version.new("2.0.0")) end it "raises a helpful error" do expect { checker.latest_resolvable_version } .to raise_error do |error| expect(error).to be_a Dependabot::GitDependencyReferenceNotFound expect(error.dependency).to eq("prius") end end end context "when updating the gem results in a conflict" do let(:dependency_files) { bundler_project_dependency_files("git_source_with_conflict") } let(:dependency_name) { "onfido" } let(:current_version) { "1.13.0" } let(:requirements) do [{ file: "Gemfile", requirement: ">= 0", groups: [], source: { type: "git", url: "https://github.com/hvssle/onfido", branch: "master", ref: "master" } }] end before do allow_any_instance_of(Dependabot::GitCommitChecker) .to receive(:branch_or_ref_in_release?) .and_return(false) git_url = "https://github.com/hvssle/onfido.git" git_header = { "content-type" => "application/x-git-upload-pack-advertisement" } stub_request(:get, git_url + "/info/refs?service=git-upload-pack") .to_return( status: 200, body: fixture("git", "upload_packs", "onfido"), headers: git_header ) allow(checker) .to receive(:latest_resolvable_version_details) .and_call_original allow(checker) .to receive(:latest_resolvable_version_details) .with(remove_git_source: true) .and_return(version: Dependabot::Bundler::Version.new("2.0.0")) end it { is_expected.to be_nil } end end context "when the gem under consideration is not the one we're checking" do let(:dependency_files) { bundler_project_dependency_files("git_source") } let(:dependency_name) { "statesman" } let(:current_version) { "1.2" } it { is_expected.to eq(Dependabot::Bundler::Version.new("3.4.1")) } context "when dealing with a private instance" do let(:dependency_files) { bundler_project_dependency_files("private_git_source") } let(:token) do Base64.encode64("x-access-token:#{github_token}").delete("\n") end before do stub_request( :get, "https://github.com/no-exist-sorry/prius.git/info/refs" \ "?service=git-upload-pack" ).with(headers: { "Authorization" => "Basic #{token}" }) .to_return(status: 401) end it "raises a helpful error" do expect { checker.latest_resolvable_version } .to raise_error do |error| expect(error).to be_a(Dependabot::GitDependenciesNotReachable) expect(error.dependency_urls) .to eq(["git@github.com:no-exist-sorry/prius"]) end end end context "when dealing with a bad reference" do let(:dependency_files) { bundler_project_dependency_files("bad_ref") } before do stub_request(:get, "https://github.com/dependabot-fixtures/prius") .to_return(status: 200) end it "raises a helpful error" do expect { checker.latest_resolvable_version } .to raise_error do |error| expect(error).to be_a Dependabot::GitDependencyReferenceNotFound expect(error.dependency).to eq("prius") end end end context "when dealing with a bad branch" do let(:dependency_files) { bundler_project_dependency_files("bad_branch") } it { is_expected.to eq(Dependabot::Bundler::Version.new("3.4.1")) } end end end context "when given a Gemfile that specifies a Ruby version" do let(:dependency_files) { bundler_project_dependency_files("explicit_ruby") } let(:dependency_name) { "statesman" } let(:requirements) do [{ file: "Gemfile", requirement: "~> 1.2.0", groups: [], source: nil }] end it { is_expected.to eq(Dependabot::Bundler::Version.new("3.4.1")) } context "when the instance is old" do let(:dependency_files) { bundler_project_dependency_files("explicit_ruby_old") } it "Gem version is 2.0.1" do skip "This test intermittently fails, which often trips up external contributors" expect(latest_resolvable_version).to eq(Dependabot::Bundler::Version.new("2.0.1")) end end end context "with a gemspec and a Gemfile" do let(:dependency_files) { bundler_project_dependency_files("imports_gemspec_small_example_no_lockfile") } let(:requirements) do [{ file: "Gemfile", requirement: "~> 1.2.0", groups: [], source: nil }, { file: "example.gemspec", requirement: "~> 1.0", groups: [], source: nil }] end it "doesn't just fall back to latest_version" do expect(checker.latest_resolvable_version) .to eq(Dependabot::Bundler::Version.new("1.13.0")) end context "when the gemspec has a path" do let(:dependency_files) { bundler_project_dependency_files("imports_gemspec_from_path") } let(:requirements) do [{ file: "Gemfile", requirement: "~> 1.2.0", groups: [], source: nil }, { file: "subdir/example.gemspec", requirement: "~> 1.0", groups: [], source: nil }] end it "doesn't just fall back to latest_version" do expect(checker.latest_resolvable_version) .to eq(Dependabot::Bundler::Version.new("1.13.0")) end end context "when an old required ruby is specified in the gemspec" do let(:dependency_files) { bundler_project_dependency_files("imports_gemspec_old_required_ruby_no_lockfile") } let(:dependency_name) { "statesman" } it "takes the minimum ruby version into account" do expect(checker.latest_resolvable_version) .to eq(Dependabot::Bundler::Version.new("2.0.1")) end end context "when the Gemfile doesn't import the gemspec" do let(:dependency_files) { bundler_project_dependency_files("gemspec_not_imported_no_lockfile") } it "falls back to latest_version" do expect(checker.latest_resolvable_version) .to eq(Dependabot::Bundler::Version.new("1.13.0")) end end end context "with only a gemspec" do let(:dependency_files) { bundler_project_dependency_files("gemspec_small_example_no_lockfile") } it "falls back to latest_version" do dummy_version_resolver = checker.send(:version_resolver, remove_git_source: false) dummy_version = Dependabot::Bundler::Version.new("0.5.0") allow(checker) .to receive(:version_resolver) .and_return(dummy_version_resolver) expect(dummy_version_resolver) .to receive(:latest_version_details) .and_return(version: dummy_version) expect(checker.latest_resolvable_version).to eq(dummy_version) end end context "with only a Gemfile" do let(:dependency_files) { bundler_project_dependency_files("no_lockfile") } it "doesn't just fall back to latest_version" do expect(checker.latest_resolvable_version) .to eq(Dependabot::Bundler::Version.new("1.13.0")) end context "when dealing with a gem with a private git source" do let(:dependency_files) { bundler_project_dependency_files("private_git_source_no_lockfile") } let(:token) do Base64.encode64("x-access-token:#{github_token}").delete("\n") end before do stub_request( :get, "https://github.com/dependabot-fixtures/does-not-exist.git/info/refs" \ "?service=git-upload-pack" ).with(headers: { "Authorization" => "Basic #{token}" }) .to_return(status: 401) end it "raises a helpful error" do expect { checker.latest_resolvable_version } .to raise_error do |error| expect(error).to be_a(Dependabot::GitDependenciesNotReachable) expect(error.dependency_urls) .to eq(["git@github.com:dependabot-fixtures/does-not-exist"]) end end end context "when dealing with a gem with a private github source" do let(:dependency_files) { bundler_project_dependency_files("private_github_source_no_lockfile") } let(:token) do Base64.encode64("x-access-token:#{github_token}").delete("\n") end before do stub_request( :get, "https://github.com/dependabot-fixtures/does-not-exist.git/info/refs" \ "?service=git-upload-pack" ).with(headers: { "Authorization" => "Basic #{token}" }) .to_return(status: 401) end it "raises a helpful error" do expect { checker.latest_resolvable_version } .to raise_error do |error| expect(error).to be_a(Dependabot::GitDependenciesNotReachable) expect(error.dependency_urls) .to eq(["https://github.com/dependabot-fixtures/does-not-exist.git"]) end end end context "when the git request raises a timeout" do let(:dependency_files) { bundler_project_dependency_files("private_git_source_no_lockfile") } let(:token) do Base64.encode64("x-access-token:#{github_token}").delete("\n") end before do stub_request( :get, "https://github.com/dependabot-fixtures/does-not-exist.git/info/refs" \ "?service=git-upload-pack" ).with(headers: { "Authorization" => "Basic #{token}" }) .to_raise(Excon::Error::Timeout) end it "raises a helpful error" do expect { checker.latest_resolvable_version } .to raise_error do |error| expect(error).to be_a(Dependabot::GitDependenciesNotReachable) expect(error.dependency_urls) .to eq(["git@github.com:dependabot-fixtures/does-not-exist"]) end end end end context "with a gem that depends on bundler" do let(:dependency_files) { bundler_project_dependency_files("guard_bundler") } let(:requirements) do [{ file: "Gemfile", requirement: "~> 2.2.1, <= 3.0.0", groups: [], source: nil }] end let(:dependency_name) { "guard-bundler" } let(:current_version) { "2.2.1" } context "when using bundler v2" do it { is_expected.to eq(Dependabot::Bundler::Version.new("3.0.0")) } end end end describe "#preferred_resolvable_version" do subject { checker.preferred_resolvable_version } include_context "when stubbing rubygems compact index" include_context "when stubbing rubygems versions api" it { is_expected.to eq(Dependabot::Bundler::Version.new("1.13.0")) } context "with a security vulnerability" do let(:security_advisories) do [ Dependabot::SecurityAdvisory.new( dependency_name: dependency_name, package_manager: "bundler", vulnerable_versions: ["<= 1.4.0"] ) ] end it { is_expected.to eq(Dependabot::Bundler::Version.new("1.5.0")) } end end describe "#latest_resolvable_version_with_no_unlock" do subject { checker.latest_resolvable_version_with_no_unlock } include_context "when stubbing rubygems compact index" include_context "when stubbing rubygems versions api" context "when given a gem from rubygems" do it { is_expected.to eq(Dependabot::Bundler::Version.new("1.4.0")) } context "with a version conflict at the latest version" do let(:dependency_files) { bundler_project_dependency_files("version_conflict_no_req_change") } let(:dependency_name) { "ibandit" } let(:current_version) { "0.1.0" } let(:requirements) do [{ file: "Gemfile", requirement: "~> 0.1", groups: [], source: nil }] end # The latest version of ibandit is 0.8.5, but 0.3.4 is the latest # version compatible with the version of i18n in the Gemfile. it { is_expected.to eq(Dependabot::Bundler::Version.new("0.3.4")) } end end context "with a sub-dependency" do let(:dependency_files) { bundler_project_dependency_files("subdependency") } let(:requirements) { [] } let(:dependency_name) { "i18n" } let(:current_version) { "0.7.0.beta1" } it { is_expected.to eq(Dependabot::Bundler::Version.new("0.7.0")) } end end describe "#updated_requirements" do subject(:updated_requirements) { checker.updated_requirements } include_context "when stubbing rubygems compact index" include_context "when stubbing rubygems versions api" let(:requirements_updater) do Dependabot::Bundler::UpdateChecker::RequirementsUpdater end before do allow(requirements_updater).to receive(:new).and_call_original end context "with a Gemfile and a Gemfile.lock" do let(:dependency_files) { bundler_project_dependency_files("gemfile") } let(:dependency_name) { "business" } let(:current_version) { "1.4.0" } let(:requirements) do [{ file: "Gemfile", requirement: "~> 1.4.0", groups: [:default], source: nil }] end it "delegates to Bundler::RequirementsUpdater with the right params" do expect(requirements_updater) .to receive(:new).with( requirements: requirements, update_strategy: Dependabot::RequirementsUpdateStrategy::BumpVersions, latest_version: "1.13.0", latest_resolvable_version: "1.13.0", updated_source: nil ).and_call_original expect(updated_requirements.count).to eq(1) expect(updated_requirements.first[:requirement]).to eq("~> 1.13.0") end context "with a security vulnerability" do let(:security_advisories) do [ Dependabot::SecurityAdvisory.new( dependency_name: dependency_name, package_manager: "bundler", vulnerable_versions: ["<= 1.4.0"] ) ] end it "delegates to Bundler::RequirementsUpdater with the right params" do expect(requirements_updater) .to receive(:new).with( requirements: requirements, update_strategy: Dependabot::RequirementsUpdateStrategy::BumpVersions, latest_version: "1.13.0", latest_resolvable_version: "1.5.0", updated_source: nil ).and_call_original expect(updated_requirements.count).to eq(1) expect(updated_requirements.first[:requirement]).to eq("~> 1.5.0") end end context "with a sub-dependency" do let(:dependency_files) { bundler_project_dependency_files("subdependency") } let(:requirements) { [] } let(:dependency_name) { "i18n" } let(:current_version) { "0.7.0.beta1" } it { is_expected.to eq([]) } end context "with a gems.rb and gems.locked" do let(:dependency_files) { bundler_project_dependency_files("gems_rb") } let(:requirements) do [{ file: "gems.rb", requirement: "~> 1.4.0", groups: [:default], source: nil }] end it "delegates to Bundler::RequirementsUpdater" do expect(requirements_updater) .to receive(:new).with( requirements: requirements, update_strategy: Dependabot::RequirementsUpdateStrategy::BumpVersions, latest_version: "1.13.0", latest_resolvable_version: "1.13.0", updated_source: nil ).and_call_original expect(updated_requirements.count).to eq(1) expect(updated_requirements.first[:requirement]).to eq("~> 1.13.0") expect(updated_requirements.first[:file]).to eq("gems.rb") end end context "when dealing with a gem with a git source" do let(:dependency_files) { bundler_project_dependency_files("git_source_with_version_gemfile") } let(:dependency_name) { "dependabot-test-ruby-package" } let(:current_version) { "81073f9462f228c6894e3e384d0718def310d99f" } let(:requirements) do [{ file: "Gemfile", requirement: "~> 1.0.0", groups: [:default], source: { type: "git", url: "https://github.com/dependabot-fixtures/" \ "dependabot-test-ruby-package", branch: "master", ref: "master" } }] end before do allow_any_instance_of(Dependabot::GitCommitChecker) .to receive(:branch_or_ref_in_release?) .and_return(false) stub_request( :get, rubygems_url + "versions/dependabot-test-ruby-package.json" ).to_return(status: 404) git_url = "https://github.com/dependabot-fixtures/" \ "dependabot-test-ruby-package.git" git_header = { "content-type" => "application/x-git-upload-pack-advertisement" } stub_request(:get, git_url + "/info/refs?service=git-upload-pack") .to_return( status: 200, body: fixture( "git", "upload_packs", "dependabot-test-ruby-package" ), headers: git_header ) end it "delegates to Bundler::RequirementsUpdater with the right params" do skip "Skipping as this fails after removing a feature flag that is rolled out to 100%" expect(requirements_updater) .to receive(:new).with( requirements: requirements, update_strategy: Dependabot::RequirementsUpdateStrategy::BumpVersions, latest_version: "1.0.1", latest_resolvable_version: "1.0.1", updated_source: requirements.first[:source] ).and_call_original expect(updated_requirements.count).to eq(1) expect(updated_requirements.first[:requirement]) .to start_with("~> 1.") end context "when the reference is pinned" do let(:dependency_files) { bundler_project_dependency_files("git_source") } let(:dependency_name) { "business" } let(:current_version) { "a1b78a929dac93a52f08db4f2847d76d6cfe39bd" } let(:requirements) do [{ file: "Gemfile", requirement: ">= 0", groups: [], source: { type: "git", url: "https://github.com/dependabot-fixtures/business", branch: "master", ref: "a1b78a9" } }] end context "when the reference is not included in the new version" do before do stub_request(:get, rubygems_url + "versions/business.json") .to_return(status: 404, body: "This rubygem could not be found.") end it "delegates to Bundler::RequirementsUpdater" do skip "Skipping as this fails after removing a feature flag that is rolled out to 100%" expect(requirements_updater) .to receive(:new).with( requirements: requirements, update_strategy: Dependabot::RequirementsUpdateStrategy::BumpVersions, latest_version: /^2./, latest_resolvable_version: /^1./, updated_source: requirements.first[:source] ).and_call_original expect(updated_requirements.count).to eq(1) expect(updated_requirements.first[:requirement]).to eq(">= 0") expect(updated_requirements.first[:source]).not_to be_nil end end end end end context "with a Gemfile, a Gemfile.lock and a gemspec" do let(:dependency_files) { bundler_project_dependency_files("imports_gemspec") } let(:dependency_name) { "business" } let(:current_version) { "1.4.0" } let(:requirements) do [{ file: "Gemfile", requirement: "~> 1.4.0", groups: [:default], source: nil }, { file: "example.gemspec", requirement: "~> 1.0", groups: [:default], source: nil }] end it "delegates to Bundler::RequirementsUpdater with the right params" do expect(requirements_updater) .to receive(:new).with( requirements: requirements, update_strategy: Dependabot::RequirementsUpdateStrategy::BumpVersions, latest_version: "1.13.0", latest_resolvable_version: "1.13.0", updated_source: requirements.first[:source] ).and_call_original expect(updated_requirements.count).to eq(2) expect(updated_requirements.first[:requirement]).to eq("~> 1.13.0") expect(updated_requirements.last[:requirement]).to eq("~> 1.0") end end context "with a Gemfile and a gemspec" do let(:dependency_files) { bundler_project_dependency_files("imports_gemspec_small_example_no_lockfile") } let(:dependency_name) { "business" } let(:current_version) { nil } let(:requirements) do [{ file: "Gemfile", requirement: "~> 1.4.0", groups: [:default], source: nil }, { file: "example.gemspec", requirement: "~> 1.0", groups: [:default], source: nil }] end it "delegates to Bundler::RequirementsUpdater with the right params" do expect(requirements_updater) .to receive(:new).with( requirements: requirements, update_strategy: Dependabot::RequirementsUpdateStrategy::BumpVersionsIfNecessary, latest_version: "1.13.0", latest_resolvable_version: "1.13.0", updated_source: requirements.first[:source] ).and_call_original expect(updated_requirements.count).to eq(2) expect(updated_requirements.first[:requirement]).to eq("~> 1.13.0") expect(updated_requirements.last[:requirement]).to eq("~> 1.0") end end context "with a Gemfile only" do let(:dependency_files) { bundler_project_dependency_files("no_lockfile") } let(:dependency_name) { "business" } let(:current_version) { nil } let(:requirements) do [{ file: "Gemfile", requirement: "~> 1.4.0", groups: [:default], source: nil }] end it "delegates to Bundler::RequirementsUpdater with the right params" do expect(requirements_updater) .to receive(:new).with( requirements: requirements, update_strategy: Dependabot::RequirementsUpdateStrategy::BumpVersionsIfNecessary, latest_version: "1.13.0", latest_resolvable_version: "1.13.0", updated_source: requirements.first[:source] ).and_call_original expect(updated_requirements.count).to eq(1) expect(updated_requirements.first[:requirement]).to eq("~> 1.13.0") end end context "with a gemspec only" do let(:dependency_files) { bundler_project_dependency_files("gemspec_no_lockfile") } let(:dependency_name) { "business" } let(:current_version) { nil } let(:requirements) do [{ file: "example.gemspec", requirement: "~> 0.9", groups: ["runtime"], source: nil }] end it "delegates to Bundler::RequirementsUpdater with the right params" do expect(requirements_updater) .to receive(:new).with( requirements: requirements, update_strategy: Dependabot::RequirementsUpdateStrategy::BumpVersionsIfNecessary, latest_version: "1.13.0", latest_resolvable_version: "1.13.0", updated_source: requirements.first[:source] ).and_call_original expect(updated_requirements.count).to eq(1) expect(updated_requirements.first[:requirement]).to eq(">= 0.9, < 2.0") end end end describe "#requirements_unlocked_or_can_be?" do subject { checker.requirements_unlocked_or_can_be? } context "with a Gemfile dependency that is already unlocked" do let(:dependency_files) { bundler_project_dependency_files("version_not_specified") } let(:requirements) do [{ file: "Gemfile", requirement: ">= 0", groups: [], source: nil }] end it { is_expected.to be(true) } context "when the lockfile-only requirements update strategy is set" do let(:requirements_update_strategy) { Dependabot::RequirementsUpdateStrategy::LockfileOnly } it { is_expected.to be(true) } end end context "with a sub-dependency" do let(:dependency_files) { bundler_project_dependency_files("subdependency") } let(:requirements) { [] } let(:dependency_name) { "i18n" } let(:current_version) { "0.7.0.beta1" } it { is_expected.to be(true) } end context "with a Gemfile dependency that can be unlocked" do let(:dependency_files) { bundler_project_dependency_files("gemfile") } let(:requirements) do [{ file: "Gemfile", requirement: req, groups: [], source: nil }] end let(:req) { "~> 1.4.0" } it { is_expected.to be(true) } context "with multiple requirements" do let(:dependency_files) { bundler_project_dependency_files("version_between_bounds_gemfile") } let(:req) { "> 1.0.0, < 1.5.0" } it { is_expected.to be(true) } end context "when the lockfile-only requirements update strategy is set" do let(:requirements_update_strategy) { Dependabot::RequirementsUpdateStrategy::LockfileOnly } it { is_expected.to be(false) } end end # For now we always let git dependencies through context "with a Gemfile dependency that is a git dependency" do let(:dependency_files) { bundler_project_dependency_files("git_source_no_ref") } let(:requirements) do [{ file: "Gemfile", requirement: ">= 0", groups: [], source: { type: "git", url: "https://github.com/dependabot-fixtures/business", branch: "master", ref: "master" } }] end it { is_expected.to be(true) } end context "with a Gemfile with a function version" do let(:dependency_files) { bundler_project_dependency_files("function_version_gemfile") } let(:requirements) do [{ file: "Gemfile", requirement: "1.0.0", groups: [], source: nil }] end it { is_expected.to be(false) } end end end