Skip to content
18 changes: 11 additions & 7 deletions bazel/lib/dependabot/bazel/update_checker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
require "time"
require "dependabot/update_checkers"
require "dependabot/update_checkers/base"
require "dependabot/update_checkers/cooldown_calculation"
require "dependabot/bazel/version"
require "dependabot/package/package_release"

Expand Down Expand Up @@ -137,7 +138,7 @@ def apply_cooldown_filter(versions)

next false unless details&.released_at

if cooldown_period?(T.must(details.released_at))
if cooldown_period?(T.must(details.released_at), version)
Dependabot.logger.info("Skipping version #{version} due to cooldown period")
true
else
Expand Down Expand Up @@ -181,19 +182,22 @@ def publication_details
)
end

sig { params(release_date: Time).returns(T::Boolean) }
def cooldown_period?(release_date)
sig { params(release_date: Time, version_string: String).returns(T::Boolean) }
def cooldown_period?(release_date, version_string)
cooldown = update_cooldown
return false unless cooldown

cooldown_days = cooldown.default_days
(Time.now.to_i - release_date.to_i) < (cooldown_days * 24 * 60 * 60)
current_version = dependency.version ? version_class.new(dependency.version) : nil
new_version = version_class.new(version_string)
days = Dependabot::UpdateCheckers::CooldownCalculation.cooldown_days_for(cooldown, current_version, new_version)
Dependabot::UpdateCheckers::CooldownCalculation.within_cooldown_window?(release_date, days)
end

sig { returns(T::Boolean) }
def should_skip_cooldown?
cooldown = update_cooldown
cooldown.nil? || !cooldown_enabled? || !cooldown.included?(dependency.name)
Dependabot::UpdateCheckers::CooldownCalculation.skip_cooldown?(
update_cooldown, dependency.name, cooldown_enabled: cooldown_enabled?
)
end

sig { returns(T::Boolean) }
Expand Down
36 changes: 22 additions & 14 deletions bazel/spec/dependabot/bazel/update_checker_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -640,7 +640,7 @@
allow(Dependabot.logger).to receive(:info)

# Mock cooldown period check - return true for recent time, false for old time
allow(checker).to receive(:cooldown_period?) do |release_time|
allow(checker).to receive(:cooldown_period?) do |release_time, _version|
(Time.now.to_i - release_time.to_i) < (24 * 60 * 60) # 24 hours
end

Expand Down Expand Up @@ -781,7 +781,14 @@
end

describe "#cooldown_period?" do
let(:cooldown_options) { instance_double(Dependabot::Package::ReleaseCooldownOptions) }
let(:cooldown_options) do
Dependabot::Package::ReleaseCooldownOptions.new(
default_days: 1,
semver_major_days: 7,
semver_minor_days: 3,
semver_patch_days: 1
)
end
let(:checker) do
described_class.new(
dependency: dependency,
Expand All @@ -791,23 +798,19 @@
)
end

before do
allow(cooldown_options).to receive(:default_days).and_return(1) # 1 day cooldown
end

context "when release is within cooldown period" do
let(:recent_release) { Time.now - (12 * 60 * 60) } # 12 hours ago

it "returns true" do
expect(checker.send(:cooldown_period?, recent_release)).to be true
it "returns true for a patch bump" do
expect(checker.send(:cooldown_period?, recent_release, "0.33.1")).to be true
end
end

context "when release is outside cooldown period" do
let(:old_release) { Time.now - (48 * 60 * 60) } # 48 hours ago

it "returns false" do
expect(checker.send(:cooldown_period?, old_release)).to be false
it "returns false for a patch bump" do
expect(checker.send(:cooldown_period?, old_release, "0.33.1")).to be false
end
end

Expand All @@ -823,7 +826,7 @@

it "returns false" do
release_time = Time.now - (12 * 60 * 60)
expect(checker.send(:cooldown_period?, release_time)).to be false
expect(checker.send(:cooldown_period?, release_time, "0.34.0")).to be false
end
end
end
Expand Down Expand Up @@ -1000,7 +1003,14 @@
end

describe "cooldown integration" do
let(:cooldown_options) { instance_double(Dependabot::Package::ReleaseCooldownOptions) }
let(:cooldown_options) do
Dependabot::Package::ReleaseCooldownOptions.new(
default_days: 1,
semver_major_days: 1,
semver_minor_days: 1,
semver_patch_days: 1
)
end
let(:checker_with_cooldown) do
described_class.new(
dependency: dependency,
Expand All @@ -1011,8 +1021,6 @@
end

before do
allow(cooldown_options).to receive(:included?).with("rules_go").and_return(true)
allow(cooldown_options).to receive(:default_days).and_return(1) # 1 day cooldown
allow(Dependabot.logger).to receive(:info)
end

Expand Down
46 changes: 18 additions & 28 deletions common/lib/dependabot/package/package_latest_version_finder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
require "dependabot/security_advisory"
require "dependabot/dependency"
require "dependabot/update_checkers/version_filters"
require "dependabot/update_checkers/cooldown_calculation"
require "dependabot/registry_client"
require "dependabot/package/package_details"
require "dependabot/package/release_cooldown_options"
Expand All @@ -19,8 +20,6 @@ class PackageLatestVersionFinder
extend T::Sig
extend T::Helpers

DAY_IN_SECONDS = T.let(24 * 60 * 60, Integer)

abstract!

sig { returns(Dependabot::Dependency) }
Expand Down Expand Up @@ -202,13 +201,18 @@ def filter_by_cooldown(releases)
def in_cooldown_period?(release)
return false unless release.released_at

current_version = version_class.correct?(dependency.version) ? version_class.new(dependency.version) : nil
days = cooldown_days_for(current_version, release.version)
cooldown = @cooldown_options
return false if Dependabot::UpdateCheckers::CooldownCalculation.skip_cooldown?(
cooldown, dependency.name, cooldown_enabled: cooldown_enabled?
)

# Calculate the number of seconds passed since the release
passed_seconds = Time.now.to_i - release.released_at.to_i
# Check if the release is within the cooldown period
passed_seconds < days * DAY_IN_SECONDS
current_version = version_class.correct?(dependency.version) ? version_class.new(dependency.version) : nil
days = Dependabot::UpdateCheckers::CooldownCalculation.cooldown_days_for(
T.must(cooldown), current_version, release.version
)
Dependabot::UpdateCheckers::CooldownCalculation.within_cooldown_window?(
T.must(release.released_at), days
)
end

sig do
Expand Down Expand Up @@ -308,27 +312,13 @@ def filter_out_of_range_versions(releases)
end
def cooldown_days_for(current_version, new_version)
cooldown = @cooldown_options
return 0 if cooldown.nil?
return 0 unless cooldown_enabled?
return 0 unless cooldown.included?(dependency.name)
return cooldown.default_days if current_version.nil?

current_version_semver = current_version.semver_parts
new_version_semver = new_version.semver_parts

# If semver_parts is nil for either, return default cooldown
return cooldown.default_days if current_version_semver.nil? || new_version_semver.nil?

# Ensure values are always integers
current_major, current_minor, current_patch = current_version_semver
new_major, new_minor, new_patch = new_version_semver

# Determine cooldown based on version difference
return cooldown.semver_major_days if new_major > current_major
return cooldown.semver_minor_days if new_minor > current_minor
return cooldown.semver_patch_days if new_patch > current_patch
return 0 if Dependabot::UpdateCheckers::CooldownCalculation.skip_cooldown?(
cooldown, dependency.name, cooldown_enabled: cooldown_enabled?
)

cooldown.default_days
Dependabot::UpdateCheckers::CooldownCalculation.cooldown_days_for(
T.must(cooldown), current_version, new_version
)
end

sig { returns(T::Boolean) }
Expand Down
23 changes: 23 additions & 0 deletions common/lib/dependabot/package/release_cooldown_options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,29 @@ def included?(dependency_name)
@include.empty? || @include.any? { |pattern| File.fnmatch?(pattern, dependency_name) }
end

sig do
params(
current_semver: T.nilable([Integer, Integer, Integer]),
new_semver: T.nilable([Integer, Integer, Integer])
).returns(Integer)
end
def cooldown_days_for(current_semver, new_semver)
return @default_days if current_semver.nil? || new_semver.nil?

current_major, current_minor, current_patch = current_semver
new_major, new_minor, new_patch = new_semver

return @semver_major_days if new_major > current_major

if new_major == current_major
return @semver_minor_days if new_minor > current_minor
return @semver_patch_days if new_minor == current_minor &&
new_patch > current_patch
end

@default_days
end

private

sig { params(dependency_name: String).returns(T::Boolean) }
Expand Down
53 changes: 53 additions & 0 deletions common/lib/dependabot/update_checkers/cooldown_calculation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# typed: strong
# frozen_string_literal: true

require "sorbet-runtime"
require "dependabot/package/release_cooldown_options"
require "dependabot/version"

module Dependabot
module UpdateCheckers
# Shared utility module for cooldown period calculations.
#
# Provides stateless module methods used by ecosystem update checkers
# to determine whether a release is within its cooldown window and
# how many cooldown days apply for a given version bump.
module CooldownCalculation
extend T::Sig

DAY_IN_SECONDS = T.let(24 * 60 * 60, Integer)

sig { params(release_date: Time, cooldown_days: Integer).returns(T::Boolean) }
def self.within_cooldown_window?(release_date, cooldown_days)
(Time.now.to_i - release_date.to_i) < (cooldown_days * DAY_IN_SECONDS)
end

sig do
params(
cooldown: Dependabot::Package::ReleaseCooldownOptions,
current_version: T.nilable(Dependabot::Version),
new_version: Dependabot::Version
).returns(Integer)
end
def self.cooldown_days_for(cooldown, current_version, new_version)
return cooldown.default_days unless current_version

cooldown.cooldown_days_for(
current_version.semver_parts,
new_version.semver_parts
)
end

sig do
params(
cooldown: T.nilable(Dependabot::Package::ReleaseCooldownOptions),
dependency_name: String,
cooldown_enabled: T::Boolean
).returns(T::Boolean)
end
def self.skip_cooldown?(cooldown, dependency_name, cooldown_enabled: true)
cooldown.nil? || !cooldown_enabled || !cooldown.included?(dependency_name)
end
end
end
end
57 changes: 57 additions & 0 deletions common/spec/dependabot/package/release_cooldown_options_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -244,4 +244,61 @@
end
end
end

describe "#cooldown_days_for" do
context "with distinct semver days" do
let(:default_days) { 5 }
let(:semver_major_days) { 14 }
let(:semver_minor_days) { 7 }
let(:semver_patch_days) { 2 }

it "returns semver_major_days for a major bump" do
expect(release_cooldown_options.cooldown_days_for([1, 0, 0], [2, 0, 0])).to eq(14)
end

it "returns semver_minor_days for a minor bump" do
expect(release_cooldown_options.cooldown_days_for([1, 0, 0], [1, 1, 0])).to eq(7)
end

it "returns semver_patch_days for a patch bump" do
expect(release_cooldown_options.cooldown_days_for([1, 0, 0], [1, 0, 1])).to eq(2)
end

it "returns default_days when versions are equal" do
expect(release_cooldown_options.cooldown_days_for([1, 0, 0], [1, 0, 0])).to eq(5)
end

it "returns default_days when current_semver is nil" do
expect(release_cooldown_options.cooldown_days_for(nil, [2, 0, 0])).to eq(5)
end

it "returns default_days when new_semver is nil" do
expect(release_cooldown_options.cooldown_days_for([1, 0, 0], nil)).to eq(5)
end

it "returns default_days when both are nil" do
expect(release_cooldown_options.cooldown_days_for(nil, nil)).to eq(5)
end

it "returns semver_major_days for a major bump that also changes minor and patch" do
expect(release_cooldown_options.cooldown_days_for([0, 33, 0], [1, 2, 3])).to eq(14)
end

it "returns default_days for a downgrade (new major < current major)" do
expect(release_cooldown_options.cooldown_days_for([2, 0, 0], [1, 5, 0])).to eq(5)
end

it "returns default_days for a minor downgrade within the same major" do
expect(release_cooldown_options.cooldown_days_for([1, 5, 0], [1, 3, 0])).to eq(5)
end

it "returns default_days for a patch downgrade within the same major and minor" do
expect(release_cooldown_options.cooldown_days_for([1, 5, 3], [1, 5, 1])).to eq(5)
end

it "returns semver_minor_days when major is equal and minor increases" do
expect(release_cooldown_options.cooldown_days_for([2, 1, 0], [2, 3, 0])).to eq(7)
end
end
end
end
Loading
Loading