diff --git a/common/lib/dependabot/dependency_group.rb b/common/lib/dependabot/dependency_group.rb index 0bd4e641794..3233238020a 100644 --- a/common/lib/dependabot/dependency_group.rb +++ b/common/lib/dependabot/dependency_group.rb @@ -1,12 +1,20 @@ # frozen_string_literal: true +require "wildcard_matcher" + module Dependabot class DependencyGroup - attr_reader :name, :rules + attr_reader :name, :rules, :dependencies def initialize(name:, rules:) @name = name @rules = rules + @dependencies = [] + end + + def contains?(dependency) + @dependencies.include?(dependency) if @dependencies.any? + rules.any? { |rule| WildcardMatcher.match?(rule, dependency.name) } end end end diff --git a/updater/lib/wildcard_matcher.rb b/common/lib/wildcard_matcher.rb similarity index 100% rename from updater/lib/wildcard_matcher.rb rename to common/lib/wildcard_matcher.rb diff --git a/common/spec/dependabot/dependency_group_spec.rb b/common/spec/dependabot/dependency_group_spec.rb index 7a5f0e0a803..e758e1e44b5 100644 --- a/common/spec/dependabot/dependency_group_spec.rb +++ b/common/spec/dependabot/dependency_group_spec.rb @@ -1,14 +1,105 @@ # frozen_string_literal: true require "dependabot/dependency_group" +require "dependabot/dependency" +# TODO: Once the Updater has been merged into Core, we should test this +# using the DependencyGroupEngine methods instead of mocking the functionality RSpec.describe Dependabot::DependencyGroup do + let(:dependency_group) { described_class.new(name: name, rules: rules) } + let(:name) { "test_group" } + let(:rules) { ["test-*"] } + + let(:test_dependency1) do + Dependabot::Dependency.new( + name: "test-dependency-1", + package_manager: "bundler", + version: "1.1.0", + requirements: [ + { + file: "Gemfile", + requirement: "~> 1.1.0", + groups: [], + source: nil + } + ] + ) + end + + let(:test_dependency2) do + Dependabot::Dependency.new( + name: "another-test-dependency", + package_manager: "bundler", + version: "1.1.0", + requirements: [ + { + file: "Gemfile", + requirement: "~> 1.1.0", + groups: [], + source: nil + } + ] + ) + end + describe "#name" do it "returns the name" do - my_dependency_group_name = "darren-from-work" - dependency_group = described_class.new(name: my_dependency_group_name, rules: anything) + expect(dependency_group.name).to eq(name) + end + end + + describe "#rules" do + it "returns a list of rules" do + expect(dependency_group.rules).to eq(rules) + end + end + + describe "#dependencies" do + context "when no dependencies are assigned to the group" do + it "returns an empty list" do + expect(dependency_group.dependencies).to eq([]) + end + end + + context "when dependencies have been assigned" do + before do + dependency_group.dependencies << test_dependency1 + end + + it "returns the dependencies" do + expect(dependency_group.dependencies).to include(test_dependency1) + expect(dependency_group.dependencies).not_to include(test_dependency2) + end + end + end + + describe "#contains?" do + context "before dependencies are assigned to the group" do + it "returns true if the dependency matches a rule" do + expect(dependency_group.dependencies).to eq([]) + expect(dependency_group.contains?(test_dependency1)).to be_truthy + end + + it "returns false if the dependency does not match a rule" do + expect(dependency_group.dependencies).to eq([]) + expect(dependency_group.contains?(test_dependency2)).to be_falsey + end + end + + context "after dependencies are assigned to the group" do + before do + dependency_group.dependencies << test_dependency1 + end + + it "returns true if the dependency is in the dependency list" do + expect(dependency_group.dependencies).to include(test_dependency1) + expect(dependency_group.contains?(test_dependency1)).to be_truthy + end - expect(dependency_group.name).to eq(my_dependency_group_name) + it "returns false if the dependency is not in the dependency list and does not match a rule" do + expect(dependency_group.dependencies).to include(test_dependency1) + expect(dependency_group.contains?(test_dependency2)).to be_falsey + end end end end diff --git a/updater/spec/dependabot/wildcard_matcher_spec.rb b/common/spec/dependabot/wildcard_matcher_spec.rb similarity index 100% rename from updater/spec/dependabot/wildcard_matcher_spec.rb rename to common/spec/dependabot/wildcard_matcher_spec.rb diff --git a/updater/lib/dependabot/dependency_group_engine.rb b/updater/lib/dependabot/dependency_group_engine.rb new file mode 100644 index 00000000000..d58968792e4 --- /dev/null +++ b/updater/lib/dependabot/dependency_group_engine.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +require "dependabot/dependency_group" + +# This class implements our strategy for keeping track of and matching dependency +# groups that are defined by users in their dependabot config file. +# +# This is a static class tied to the lifecycle of a Job +# - Each UpdateJob registers its own DependencyGroupEngine which calculates +# the grouped and ungrouped dependencies for a DependencySnapshot +# - Groups are only calculated once after the Job has registered its dependencies +# - All allowed dependencies should be passed in to the calculate_dependency_groups! method +# +# **Note:** This is currently an experimental feature which is not supported +# in the service or as an integration point. +# +module Dependabot + module DependencyGroupEngine + @groups_calculated = false + @registered_groups = [] + + @dependency_groups = {} + @ungrouped_dependencies = [] + + def self.reset! + @groups_calculated = false + @registered_groups = [] + + @dependency_groups = {} + @ungrouped_dependencies = [] + end + + # Eventually the key for a dependency group should be a hash since names _can_ conflict within jobs + def self.register(name, rules) + @registered_groups.push Dependabot::DependencyGroup.new(name: name, rules: rules) + end + + def self.groups_for(dependency) + return [] if dependency.nil? + return [] unless dependency.instance_of?(Dependabot::Dependency) + + @registered_groups.select do |group| + group.contains?(dependency) + end + end + + # { group_name => [DependencyGroup], ... } + def self.dependency_groups(dependencies) + return @dependency_groups if @groups_calculated + + @groups_calculated = calculate_dependency_groups!(dependencies) + + @dependency_groups + end + + # Returns a list of dependencies that do not belong to any of the groups + def self.ungrouped_dependencies(dependencies) + return @ungrouped_dependencies if @groups_calculated + + @groups_calculated = calculate_dependency_groups!(dependencies) + + @ungrouped_dependencies + end + + def self.calculate_dependency_groups!(dependencies) + dependencies.each do |dependency| + groups = groups_for(dependency) + + @ungrouped_dependencies << dependency if groups.empty? + + groups.each do |group| + group.dependencies.push(dependency) + @dependency_groups[group.name.to_sym] = group + end + end + + true + end + end +end diff --git a/updater/lib/dependabot/dependency_snapshot.rb b/updater/lib/dependabot/dependency_snapshot.rb index 26a04b7c7ef..2645d657bfd 100644 --- a/updater/lib/dependabot/dependency_snapshot.rb +++ b/updater/lib/dependabot/dependency_snapshot.rb @@ -53,6 +53,18 @@ def job_dependencies end end + # A dependency snapshot will always have the same set of dependencies since it only depends + # on the Job and dependency groups, which are static for a given commit. + def groups + # The DependencyGroupEngine registers dependencies when the Job is created + # and it will memoize the dependency groups + Dependabot::DependencyGroupEngine.dependency_groups(allowed_dependencies) + end + + def ungrouped_dependencies + Dependabot::DependencyGroupEngine.ungrouped_dependencies(allowed_dependencies) + end + private def initialize(job:, base_commit_sha:, dependency_files:) diff --git a/updater/lib/dependabot/job.rb b/updater/lib/dependabot/job.rb index 899e0431007..5f5c609be1e 100644 --- a/updater/lib/dependabot/job.rb +++ b/updater/lib/dependabot/job.rb @@ -2,6 +2,7 @@ require "dependabot/config/ignore_condition" require "dependabot/config/update_config" +require "dependabot/dependency_group_engine" require "dependabot/experiments" require "dependabot/source" require "wildcard_matcher" @@ -37,6 +38,7 @@ class Job update_subdependencies updating_a_pull_request vendor_dependencies + dependency_groups ) attr_reader :allowed_updates, @@ -51,7 +53,8 @@ class Job :security_updates_only, :source, :token, - :vendor_dependencies + :vendor_dependencies, + :dependency_groups def self.new_fetch_job(job_id:, job_definition:, repo_contents_path: nil) attrs = standardise_keys(job_definition["job"]).slice(*PERMITTED_KEYS) @@ -94,8 +97,10 @@ def initialize(attributes) @update_subdependencies = attributes.fetch(:update_subdependencies) @updating_a_pull_request = attributes.fetch(:updating_a_pull_request) @vendor_dependencies = attributes.fetch(:vendor_dependencies, false) + @dependency_groups = attributes.fetch(:dependency_groups, []) register_experiments + register_dependency_groups end def clone? @@ -233,6 +238,14 @@ def security_advisories_for(dependency) end end + def register_dependency_groups + return if dependency_groups.nil? + + dependency_groups.each do |group| + Dependabot::DependencyGroupEngine.register(group["name"], group["rules"]["patterns"]) + end + end + def ignore_conditions_for(dependency) update_config.ignored_versions_for( dependency, diff --git a/updater/lib/dependabot/updater/operations/group_update_all_versions.rb b/updater/lib/dependabot/updater/operations/group_update_all_versions.rb index af83ff35a62..3e8c066c26f 100644 --- a/updater/lib/dependabot/updater/operations/group_update_all_versions.rb +++ b/updater/lib/dependabot/updater/operations/group_update_all_versions.rb @@ -17,8 +17,6 @@ module Dependabot class Updater module Operations class GroupUpdateAllVersions - GROUP_NAME_PLACEHOLDER = "*" - def self.applies_to?(job:) return false if job.security_updates_only? return false if job.updating_a_pull_request? @@ -36,43 +34,47 @@ def initialize(service:, job:, dependency_snapshot:, error_handler:) @job = job @dependency_snapshot = dependency_snapshot @error_handler = error_handler - # This is a placeholder for a real rule object obtained from config in future - @dependency_group = Dependabot::DependencyGroup.new(name: GROUP_NAME_PLACEHOLDER, rules: []) end + # rubocop:disable Metrics/AbcSize def perform - Dependabot.logger.info("[Experimental] Starting grouped update job for #{job.source.repo}") - # We should log the rule being executed, let's just hard-code wildcard for now - # since the prototype makes best-effort to do everything in one pass. - Dependabot.logger.info("Starting update group for '#{GROUP_NAME_PLACEHOLDER}'") - dependency_change = compile_all_dependency_changes - - if dependency_change.updated_dependencies.any? - Dependabot.logger.info("Creating a pull request for '#{GROUP_NAME_PLACEHOLDER}'") - begin - service.create_pull_request(dependency_change, dependency_snapshot.base_commit_sha) - rescue StandardError => e - # FIXME: This is a workround for not having a single Dependency to report against - # - # We could use all_updated_deps.first, but that could be misleading. It may - # make more sense to handle the dependency group as a Dependancy-ish object - group_dependency = OpenStruct.new(name: "group-all") - raise if ErrorHandler::RUN_HALTING_ERRORS.keys.any? { |err| e.is_a?(err) } - - error_handler.handle_dependabot_error(error: e, dependency: group_dependency) + # FIXME: This preserves the default behavior of grouping all updates into a single PR + # but we should figure out if this is the default behavior we want. + register_all_dependencies_group unless job.dependency_groups&.any? + + dependency_snapshot.groups.each do |_group_hash, group| + Dependabot.logger.info("[Experimental] Starting grouped update job for #{job.source.repo}") + Dependabot.logger.info("Starting update group for '#{group.name}'") + + dependency_change = compile_all_dependency_changes_for(group) + + if dependency_change.updated_dependencies.any? + Dependabot.logger.info("Creating a pull request for '#{group.name}'") + begin + service.create_pull_request(dependency_change, dependency_snapshot.base_commit_sha) + rescue StandardError => e + # FIXME: This is a workround for not having a single Dependency to report against + # + # We could use all_updated_deps.first, but that could be misleading. It may + # make more sense to handle the dependency group as a Dependancy-ish object + group_dependency = OpenStruct.new(name: "group-all") + raise if ErrorHandler::RUN_HALTING_ERRORS.keys.any? { |err| e.is_a?(err) } + + error_handler.handle_dependabot_error(error: e, dependency: group_dependency) + end + else + Dependabot.logger.info("Nothing to update for Dependency Group: '#{group.name}'") end - else - Dependabot.logger.info("Nothing to update for Dependency Group: '#{GROUP_NAME_PLACEHOLDER}'") end - end - - private - attr_reader :job, - :service, - :dependency_snapshot, - :error_handler, - :dependency_group + # FIXME: Let's not worry about this for now but eventually we'd try to do single updates + # for anything that didn't appear in _at least one_ group. + # dependency_snapshot.ungrouped_dependencies.each do |dependency| + # dependency_change = create_dependency_change_for(dependency) + # create_pull_request(dependency_change) + # end + end + # rubocop:enable Metrics/AbcSize def dependencies if dependency_snapshot.dependencies.any? && dependency_snapshot.allowed_dependencies.none? @@ -83,12 +85,27 @@ def dependencies dependency_snapshot.allowed_dependencies end + private + + attr_reader :job, + :service, + :dependency_snapshot, + :error_handler + + def register_all_dependencies_group + all_dependencies_group = { "name" => "group-all", "rules" => { "patterns" => ["*"] } } + Dependabot::DependencyGroupEngine.register(all_dependencies_group["name"], + all_dependencies_group["rules"]["patterns"]) + end + # Returns a Dependabot::DependencyChange object that encapsulates the # outcome of attempting to update every dependency iteratively which # can be used for PR creation. - def compile_all_dependency_changes + def compile_all_dependency_changes_for(group) all_updated_dependencies = [] updated_files = dependencies.inject(dependency_snapshot.dependency_files) do |dependency_files, dependency| + next dependency_files unless group.contains?(dependency) + updated_dependencies = compile_updates_for(dependency, dependency_files) if updated_dependencies.any? @@ -96,7 +113,7 @@ def compile_all_dependency_changes dep.name.casecmp(dependency.name).zero? end - dependency_change = create_change_for(lead_dependency, updated_dependencies, dependency_files) + dependency_change = create_change_for(lead_dependency, updated_dependencies, dependency_files, group) # Move on to the next dependency using the existing files if we # could not create a change for any reason @@ -125,7 +142,7 @@ def compile_all_dependency_changes job: job, updated_dependencies: all_updated_dependencies, updated_dependency_files: updated_files, - dependency_group: dependency_group + dependency_group: group ) end @@ -133,7 +150,7 @@ def compile_all_dependency_changes # list of dependencies to be updated # # This method **must** return false in the event of an error - def create_change_for(lead_dependency, updated_dependencies, dependency_files) + def create_change_for(lead_dependency, updated_dependencies, dependency_files, dependency_group) Dependabot::DependencyChangeBuilder.create_from( job: job, dependency_files: dependency_files, diff --git a/updater/spec/dependabot/dependency_change_builder_spec.rb b/updater/spec/dependabot/dependency_change_builder_spec.rb index 19ea8e012ab..2c5779c7fd7 100644 --- a/updater/spec/dependabot/dependency_change_builder_spec.rb +++ b/updater/spec/dependabot/dependency_change_builder_spec.rb @@ -107,6 +107,9 @@ context "when the source is a dependency group" do let(:change_source) do + # FIXME: rules are actually a hash but for the purposes of this pass we can leave it as a list + # Once this is refactored we should create a DependencyGroup like so + # Dependabot::DependencyGroup.new(name: "dummy-pkg-*", rules: { "patterns" => ["dummy-pkg-*"] }) Dependabot::DependencyGroup.new(name: "dummy-pkg-*", rules: ["dummy-pkg-*"]) end diff --git a/updater/spec/dependabot/dependency_group_engine_spec.rb b/updater/spec/dependabot/dependency_group_engine_spec.rb new file mode 100644 index 00000000000..ef414d2a838 --- /dev/null +++ b/updater/spec/dependabot/dependency_group_engine_spec.rb @@ -0,0 +1,224 @@ +# frozen_string_literal: true + +require "spec_helper" +require "dependabot/dependency_group_engine" +require "dependabot/dependency_snapshot" +require "dependabot/job" + +# The DependencyGroupEngine is not accessed directly, but though a DependencySnapshot. +# So these tests use DependencySnapshot methods to check the DependencyGroupEngine works as expected +RSpec.describe Dependabot::DependencyGroupEngine do + let(:dependency_group_engine) { described_class } + + let(:job_json) { fixture("jobs/job_with_dependency_groups.json") } + + let(:dependency_files) do + [ + Dependabot::DependencyFile.new( + name: "Gemfile", + content: fixture("bundler/grouped/Gemfile"), + directory: "/" + ), + Dependabot::DependencyFile.new( + name: "Gemfile.lock", + content: fixture("bundler/grouped/Gemfile.lock"), + directory: "/" + ) + ] + end + + let(:base_commit_sha) do + "mock-sha" + end + + let(:encoded_dependency_files) do + dependency_files.map do |file| + base64_file = file.dup + base64_file.content = Base64.encode64(file.content) + base64_file.to_h + end + end + + let(:job_definition) do + { + "base_commit_sha" => base_commit_sha, + "base64_dependency_files" => encoded_dependency_files + } + end + + let(:source) do + Dependabot::Source.new( + provider: "github", + repo: "dependabot-fixtures/dependabot-test-ruby-package", + directory: "/", + branch: nil, + api_endpoint: "https://api.github.com/", + hostname: "github.com" + ) + end + + let(:job) do + Dependabot::Job.new_update_job( + job_id: anything, + job_definition: JSON.parse(job_json), + repo_contents_path: anything + ) + end + + def create_dependency_snapshot + Dependabot::DependencySnapshot.create_from_job_definition( + job: job, + job_definition: job_definition + ) + end + + describe "#register" do + before do + dependency_group_engine.reset! + end + + it "registers the dependency groups" do + expect(dependency_group_engine.instance_variable_get(:@registered_groups)).to eq([]) + + # We have to call the original here for the DependencyGroupEngine to actually register the groups + expect(dependency_group_engine).to receive(:register).with("group-a", ["dummy-pkg-*"]).and_call_original + expect(dependency_group_engine).to receive(:register).with("group-b", ["dummy-pkg-b"]).and_call_original + + # Groups are registered by the job when a DependencySnapshot is created + create_dependency_snapshot + + expect(dependency_group_engine.instance_variable_get(:@registered_groups)).not_to eq([]) + end + end + + describe "#groups_for" do + before do + dependency_group_engine.reset! + end + + it "returns the expected groups" do + snapshot = create_dependency_snapshot + + expect(dependency_group_engine.send(:groups_for, snapshot.dependencies[0]).count).to eq(1) + expect(dependency_group_engine.send(:groups_for, snapshot.dependencies[1]).count).to eq(2) + expect(dependency_group_engine.send(:groups_for, snapshot.dependencies[2]).count).to eq(0) + end + end + + describe "#dependency_groups" do + before do + dependency_group_engine.reset! + end + + it "returns the dependency groups" do + snapshot = create_dependency_snapshot + allowed_dependencies = snapshot.allowed_dependencies + + expect(dependency_group_engine).to receive(:dependency_groups). + with(allowed_dependencies).at_least(:once).and_call_original + expect(dependency_group_engine).to receive(:calculate_dependency_groups!). + with(allowed_dependencies).once.and_call_original + + groups = snapshot.groups + + expect(groups.key?(:"group-a")).to be_truthy + expect(groups.key?(:"group-b")).to be_truthy + expect(groups[:"group-a"]).to be_a(Dependabot::DependencyGroup) + end + + it "does not call calculate_dependency_groups! again after groups are initially calculated" do + snapshot = create_dependency_snapshot + allowed_dependencies = snapshot.allowed_dependencies + + expect(dependency_group_engine.instance_variable_get(:@groups_calculated)).to be_falsey + expect(dependency_group_engine).to receive(:calculate_dependency_groups!). + with(allowed_dependencies).once.and_call_original + + snapshot.groups + snapshot.ungrouped_dependencies + + expect(dependency_group_engine.instance_variable_get(:@groups_calculated)).to be_truthy + end + end + + describe "#ungrouped_dependencies" do + before do + dependency_group_engine.reset! + end + + it "returns the ungrouped dependencies" do + snapshot = create_dependency_snapshot + allowed_dependencies = snapshot.allowed_dependencies + + expect(dependency_group_engine).to receive(:calculate_dependency_groups!). + with(allowed_dependencies).once.and_call_original + expect(dependency_group_engine).to receive(:ungrouped_dependencies). + with(allowed_dependencies).at_least(:once).and_call_original + + ungrouped_dependencies = snapshot.ungrouped_dependencies + + expect(ungrouped_dependencies.first).to be_a(Dependabot::Dependency) + end + + it "does not call calculate_dependency_groups! again after ungrouped_dependencies are initially calculated" do + snapshot = create_dependency_snapshot + allowed_dependencies = snapshot.allowed_dependencies + + expect(dependency_group_engine.instance_variable_get(:@groups_calculated)).to be_falsey + expect(dependency_group_engine).to receive(:calculate_dependency_groups!). + with(allowed_dependencies).once.and_call_original + + snapshot.ungrouped_dependencies + snapshot.groups + + expect(dependency_group_engine.instance_variable_get(:@groups_calculated)).to be_truthy + end + end + + describe "#reset!" do + before do + dependency_group_engine.reset! + end + + it "resets the dependency group engine" do + snapshot = create_dependency_snapshot + snapshot.groups + + expect(dependency_group_engine.instance_variable_get(:@groups_calculated)).to be_truthy + expect(dependency_group_engine.instance_variable_get(:@registered_groups)).not_to eq([]) + expect(dependency_group_engine.instance_variable_get(:@dependency_groups)).not_to eq({}) + expect(dependency_group_engine.instance_variable_get(:@ungrouped_dependencies)).not_to eq([]) + + dependency_group_engine.reset! + + expect(dependency_group_engine.instance_variable_get(:@groups_calculated)).to be_falsey + expect(dependency_group_engine.instance_variable_get(:@registered_groups)).to eq([]) + expect(dependency_group_engine.instance_variable_get(:@dependency_groups)).to eq({}) + expect(dependency_group_engine.instance_variable_get(:@ungrouped_dependencies)).to eq([]) + end + end + + describe "#calculate_dependency_groups!" do + before do + dependency_group_engine.reset! + end + + it "runs once" do + snapshot = create_dependency_snapshot + allowed_dependencies = snapshot.allowed_dependencies + + expect(dependency_group_engine).to receive(:calculate_dependency_groups!). + with(allowed_dependencies).once.and_call_original + + snapshot.groups + snapshot.groups + end + + it "returns true" do + snapshot = create_dependency_snapshot + allowed_dependencies = snapshot.allowed_dependencies + + expect(dependency_group_engine.calculate_dependency_groups!(allowed_dependencies)).to be_truthy + end + end +end diff --git a/updater/spec/dependabot/job_spec.rb b/updater/spec/dependabot/job_spec.rb index a9e51b81752..92118cdfb4e 100644 --- a/updater/spec/dependabot/job_spec.rb +++ b/updater/spec/dependabot/job_spec.rb @@ -39,7 +39,8 @@ vendor_dependencies: vendor_dependencies, experiments: experiments, commit_message_options: commit_message_options, - security_updates_only: security_updates_only + security_updates_only: security_updates_only, + dependency_groups: dependency_groups } end @@ -62,6 +63,7 @@ let(:experiments) { nil } let(:commit_message_options) { nil } let(:vendor_dependencies) { false } + let(:dependency_groups) { [] } describe "::new_update_job" do let(:job_json) { fixture("jobs/job_with_credentials.json") } @@ -85,6 +87,11 @@ expect(ruby_credential["host"]).to eql("my.rubygems-host.org") expect(ruby_credential.keys).not_to include("token") end + + it "will register its dependency groups" do + expect_any_instance_of(described_class).to receive(:register_dependency_groups) + new_update_job + end end describe "#allowed_update?" do diff --git a/updater/spec/dependabot/updater/operations_spec.rb b/updater/spec/dependabot/updater/operations_spec.rb index c18690dcfbd..58b4c5e19d8 100644 --- a/updater/spec/dependabot/updater/operations_spec.rb +++ b/updater/spec/dependabot/updater/operations_spec.rb @@ -7,6 +7,10 @@ RSpec.describe Dependabot::Updater::Operations do describe "::class_for" do + before do + Dependabot::Experiments.reset! + end + it "returns nil if no operation matches" do # We always expect jobs that update a pull request to specify their # existing dependency changes, a job with this set of conditions diff --git a/updater/spec/fixtures/bundler/grouped/Gemfile b/updater/spec/fixtures/bundler/grouped/Gemfile new file mode 100644 index 00000000000..01ede77b86f --- /dev/null +++ b/updater/spec/fixtures/bundler/grouped/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +gem "dummy-pkg-a", "~> 2.0.0" +gem "dummy-pkg-b", "~> 1.1.0" +gem "ungrouped-dummy-pkg-c", "~> 1.0.0" diff --git a/updater/spec/fixtures/bundler/grouped/Gemfile.lock b/updater/spec/fixtures/bundler/grouped/Gemfile.lock new file mode 100644 index 00000000000..48feef3532e --- /dev/null +++ b/updater/spec/fixtures/bundler/grouped/Gemfile.lock @@ -0,0 +1,18 @@ +GEM + remote: https://rubygems.org/ + specs: + dummy-pkg-a (2.0.0) + dummy-pkg-b (1.1.0) + dummy-pkg-a (~> 2.0) + ungrouped-dummy-pkg-c (1.0.0) + +PLATFORMS + ruby + +DEPENDENCIES + dummy-pkg-a (~> 2.0.0) + dummy-pkg-b (~> 1.1.0) + ungrouped-dummy-pkg-c (~> 1.0.0) + +BUNDLED WITH + 1.14.6 diff --git a/updater/spec/fixtures/jobs/job_with_dependency_groups.json b/updater/spec/fixtures/jobs/job_with_dependency_groups.json new file mode 100644 index 00000000000..299b4088db9 --- /dev/null +++ b/updater/spec/fixtures/jobs/job_with_dependency_groups.json @@ -0,0 +1,52 @@ +{ + "job": { + "allowed-updates": [ + { + "dependency-type": "direct", + "update-type": "all" + } + ], + "credentials-metadata": [ + { + "type": "git_source", + "host": "github.com" + } + ], + "dependencies": null, + "directory": "/", + "existing-pull-requests": [], + "ignore-conditions": [], + "security-advisories": [], + "package-manager": "bundler", + "repo-name": "dependabot-fixtures/dependabot-test-ruby-package", + "source": { + "provider": "github", + "repo": "dependabot-fixtures/dependabot-test-ruby-package", + "directory": "/", + "branch": null, + "hostname": "github.com", + "api-endpoint": "https://api.github.com/" + }, + "lockfile-only": false, + "requirements-update-strategy": null, + "update-subdependencies": false, + "updating-a-pull-request": false, + "vendor-dependencies": false, + "security-updates-only": false, + "dependency-groups": [ + { + "name": "group-a", + "rules": { + "patterns": ["dummy-pkg-*"] + } + }, + { + "name": "group-b", + "rules": { + "patterns": ["dummy-pkg-b"] + } + } + ], + "experiments": { "grouped_updates_prototype": true } + } +}