diff options
Diffstat (limited to 'activesupport/test')
39 files changed, 975 insertions, 93 deletions
diff --git a/activesupport/test/cache/behaviors/cache_increment_decrement_behavior.rb b/activesupport/test/cache/behaviors/cache_increment_decrement_behavior.rb index 2fa2d7af88..16b7abc679 100644 --- a/activesupport/test/cache/behaviors/cache_increment_decrement_behavior.rb +++ b/activesupport/test/cache/behaviors/cache_increment_decrement_behavior.rb @@ -8,7 +8,9 @@ module CacheIncrementDecrementBehavior assert_equal 2, @cache.read("foo").to_i assert_equal 3, @cache.increment("foo") assert_equal 3, @cache.read("foo").to_i - assert_nil @cache.increment("bar") + + missing = @cache.increment("bar") + assert(missing.nil? || missing == 1) end def test_decrement @@ -18,6 +20,8 @@ module CacheIncrementDecrementBehavior assert_equal 2, @cache.read("foo").to_i assert_equal 1, @cache.decrement("foo") assert_equal 1, @cache.read("foo").to_i - assert_nil @cache.decrement("bar") + + missing = @cache.decrement("bar") + assert(missing.nil? || missing == -1) end end diff --git a/activesupport/test/cache/behaviors/cache_store_behavior.rb b/activesupport/test/cache/behaviors/cache_store_behavior.rb index 582e902f72..73a9b2a71c 100644 --- a/activesupport/test/cache/behaviors/cache_store_behavior.rb +++ b/activesupport/test/cache/behaviors/cache_store_behavior.rb @@ -146,6 +146,16 @@ module CacheStoreBehavior assert_nil @cache.read("foo") end + def test_read_and_write_uncompressed_small_data + @cache.write("foo", "bar", compress: false) + assert_equal "bar", @cache.read("foo") + end + + def test_read_and_write_uncompressed_nil + @cache.write("foo", nil, compress: false) + assert_nil @cache.read("foo") + end + def test_cache_key obj = Object.new def obj.cache_key diff --git a/activesupport/test/cache/behaviors/local_cache_behavior.rb b/activesupport/test/cache/behaviors/local_cache_behavior.rb index 8dec8090b1..f7302df4c8 100644 --- a/activesupport/test/cache/behaviors/local_cache_behavior.rb +++ b/activesupport/test/cache/behaviors/local_cache_behavior.rb @@ -20,7 +20,11 @@ module LocalCacheBehavior end def test_cleanup_clears_local_cache_but_not_remote_cache - skip unless @cache.class.instance_methods(false).include?(:cleanup) + begin + @cache.cleanup + rescue NotImplementedError + skip + end @cache.with_local_cache do @cache.write("foo", "bar") diff --git a/activesupport/test/cache/cache_entry_test.rb b/activesupport/test/cache/cache_entry_test.rb index 51b214ad8f..80ff7ad564 100644 --- a/activesupport/test/cache/cache_entry_test.rb +++ b/activesupport/test/cache/cache_entry_test.rb @@ -14,16 +14,23 @@ class CacheEntryTest < ActiveSupport::TestCase end end - def test_compress_values + def test_compressed_values value = "value" * 100 entry = ActiveSupport::Cache::Entry.new(value, compress: true, compress_threshold: 1) assert_equal value, entry.value assert(value.bytesize > entry.size, "value is compressed") end - def test_non_compress_values + def test_compressed_by_default value = "value" * 100 - entry = ActiveSupport::Cache::Entry.new(value) + entry = ActiveSupport::Cache::Entry.new(value, compress_threshold: 1) + assert_equal value, entry.value + assert(value.bytesize > entry.size, "value is compressed") + end + + def test_uncompressed_values + value = "value" * 100 + entry = ActiveSupport::Cache::Entry.new(value, compress: false) assert_equal value, entry.value assert_equal value.bytesize, entry.size end diff --git a/activesupport/test/cache/stores/file_store_test.rb b/activesupport/test/cache/stores/file_store_test.rb index 391ab60b3a..66231b0a82 100644 --- a/activesupport/test/cache/stores/file_store_test.rb +++ b/activesupport/test/cache/stores/file_store_test.rb @@ -118,6 +118,7 @@ class FileStoreTest < ActiveSupport::TestCase assert_not @cache.exist?("foo") assert @cache.exist?("baz") assert @cache.exist?("quux") + assert_equal 2, Dir.glob(File.join(cache_dir, "**")).size end end diff --git a/activesupport/test/cache/stores/mem_cache_store_test.rb b/activesupport/test/cache/stores/mem_cache_store_test.rb index becc1f7fbf..1b73fb65eb 100644 --- a/activesupport/test/cache/stores/mem_cache_store_test.rb +++ b/activesupport/test/cache/stores/mem_cache_store_test.rb @@ -24,7 +24,7 @@ class MemCacheStoreTest < ActiveSupport::TestCase @data = @cache.instance_variable_get(:@data) @cache.clear @cache.silence! - @cache.logger = ActiveSupport::Logger.new("/dev/null") + @cache.logger = ActiveSupport::Logger.new(File::NULL) end include CacheStoreBehavior diff --git a/activesupport/test/cache/stores/redis_cache_store_test.rb b/activesupport/test/cache/stores/redis_cache_store_test.rb new file mode 100644 index 0000000000..988de9207f --- /dev/null +++ b/activesupport/test/cache/stores/redis_cache_store_test.rb @@ -0,0 +1,151 @@ +# frozen_string_literal: true + +require "abstract_unit" +require "active_support/cache" +require "active_support/cache/redis_cache_store" +require_relative "../behaviors" + +module ActiveSupport::Cache::RedisCacheStoreTests + class LookupTest < ActiveSupport::TestCase + test "may be looked up as :redis_cache_store" do + assert_kind_of ActiveSupport::Cache::RedisCacheStore, + ActiveSupport::Cache.lookup_store(:redis_cache_store) + end + end + + class InitializationTest < ActiveSupport::TestCase + test "omitted URL uses Redis client with default settings" do + assert_called_with Redis, :new, [ + url: nil, + connect_timeout: 20, read_timeout: 1, write_timeout: 1, + reconnect_attempts: 0, + ] do + build + end + end + + test "no URLs uses Redis client with default settings" do + assert_called_with Redis, :new, [ + url: nil, + connect_timeout: 20, read_timeout: 1, write_timeout: 1, + reconnect_attempts: 0, + ] do + build url: [] + end + end + + test "singular URL uses Redis client" do + assert_called_with Redis, :new, [ + url: "redis://localhost:6379/0", + connect_timeout: 20, read_timeout: 1, write_timeout: 1, + reconnect_attempts: 0, + ] do + build url: "redis://localhost:6379/0" + end + end + + test "one URL uses Redis client" do + assert_called_with Redis, :new, [ + url: "redis://localhost:6379/0", + connect_timeout: 20, read_timeout: 1, write_timeout: 1, + reconnect_attempts: 0, + ] do + build url: %w[ redis://localhost:6379/0 ] + end + end + + test "multiple URLs uses Redis::Distributed client" do + assert_called_with Redis, :new, [ + [ url: "redis://localhost:6379/0", + connect_timeout: 20, read_timeout: 1, write_timeout: 1, + reconnect_attempts: 0 ], + [ url: "redis://localhost:6379/1", + connect_timeout: 20, read_timeout: 1, write_timeout: 1, + reconnect_attempts: 0 ], + ], returns: Redis.new do + @cache = build url: %w[ redis://localhost:6379/0 redis://localhost:6379/1 ] + assert_kind_of ::Redis::Distributed, @cache.redis + end + end + + test "block argument uses yielded client" do + block = -> { :custom_redis_client } + assert_called block, :call do + build redis: block + end + end + + private + def build(**kwargs) + ActiveSupport::Cache::RedisCacheStore.new(**kwargs).tap do |cache| + cache.redis + end + end + end + + class StoreTest < ActiveSupport::TestCase + setup do + @namespace = "namespace" + + @cache = ActiveSupport::Cache::RedisCacheStore.new(timeout: 0.1, namespace: @namespace, expires_in: 60) + #@cache.logger = Logger.new($stdout) # For test debugging + + # For LocalCacheBehavior tests + @peek = ActiveSupport::Cache::RedisCacheStore.new(timeout: 0.1, namespace: @namespace) + end + + teardown do + @cache.clear + @cache.redis.disconnect! + end + end + + class RedisCacheStoreCommonBehaviorTest < StoreTest + include CacheStoreBehavior + include CacheStoreVersionBehavior + include LocalCacheBehavior + include CacheIncrementDecrementBehavior + include AutoloadingCacheBehavior + end + + # Separate test class so we can omit the namespace which causes expected, + # appropriate complaints about incompatible string encodings. + class KeyEncodingSafetyTest < StoreTest + include EncodedKeyCacheBehavior + + setup do + @cache = ActiveSupport::Cache::RedisCacheStore.new(timeout: 0.1) + @cache.logger = nil + end + end + + class StoreAPITest < StoreTest + end + + class FailureSafetyTest < StoreTest + test "fetch read failure returns nil" do + end + + test "fetch read failure does not attempt to write" do + end + + test "write failure returns nil" do + end + end + + class DeleteMatchedTest < StoreTest + test "deletes keys matching glob" do + @cache.write("foo", "bar") + @cache.write("fu", "baz") + @cache.delete_matched("foo*") + assert !@cache.exist?("foo") + assert @cache.exist?("fu") + end + + test "fails with regexp matchers" do + assert_raise ArgumentError do + @cache.delete_matched(/OO/i) + end + end + end +end diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb index 3902e41a60..5f894db46f 100644 --- a/activesupport/test/callbacks_test.rb +++ b/activesupport/test/callbacks_test.rb @@ -193,13 +193,6 @@ module CallbacksTest before_save Proc.new { |r| r.history << "b00m" }, if: :no before_save Proc.new { |r| r.history << [:before_save, :symbol] }, unless: :no before_save Proc.new { |r| r.history << "b00m" }, unless: :yes - # string - ActiveSupport::Deprecation.silence do - before_save Proc.new { |r| r.history << [:before_save, :string] }, if: "yes" - before_save Proc.new { |r| r.history << "b00m" }, if: "no" - before_save Proc.new { |r| r.history << [:before_save, :string] }, unless: "no" - before_save Proc.new { |r| r.history << "b00m" }, unless: "yes" - end # Combined if and unless before_save Proc.new { |r| r.history << [:before_save, :combined_symbol] }, if: :yes, unless: :no before_save Proc.new { |r| r.history << "b00m" }, if: :yes, unless: :yes @@ -592,8 +585,6 @@ module CallbacksTest [:before_save, :proc], [:before_save, :symbol], [:before_save, :symbol], - [:before_save, :string], - [:before_save, :string], [:before_save, :combined_symbol], ], person.history end @@ -1182,14 +1173,15 @@ module CallbacksTest end end - class DeprecatedWarningTest < ActiveSupport::TestCase - def test_deprecate_string_conditional_options + class NotSupportedStringConditionalTest < ActiveSupport::TestCase + def test_string_conditional_options klass = Class.new(Record) - assert_deprecated { klass.before_save :tweedle, if: "true" } - assert_deprecated { klass.after_save :tweedle, unless: "false" } - assert_deprecated { klass.skip_callback :save, :before, :tweedle, if: "true" } - assert_deprecated { klass.skip_callback :save, :after, :tweedle, unless: "false" } + assert_raises(ArgumentError) { klass.before_save :tweedle, if: ["true"] } + assert_raises(ArgumentError) { klass.before_save :tweedle, if: "true" } + assert_raises(ArgumentError) { klass.after_save :tweedle, unless: "false" } + assert_raises(ArgumentError) { klass.skip_callback :save, :before, :tweedle, if: "true" } + assert_raises(ArgumentError) { klass.skip_callback :save, :after, :tweedle, unless: "false" } end end diff --git a/activesupport/test/clean_backtrace_test.rb b/activesupport/test/clean_backtrace_test.rb index e9361ecd26..1b44c7c9bf 100644 --- a/activesupport/test/clean_backtrace_test.rb +++ b/activesupport/test/clean_backtrace_test.rb @@ -10,8 +10,8 @@ class BacktraceCleanerFilterTest < ActiveSupport::TestCase test "backtrace should filter all lines in a backtrace, removing prefixes" do assert_equal \ - ["/my/class.rb", "/my/module.rb"], - @bc.clean(["/my/prefix/my/class.rb", "/my/prefix/my/module.rb"]) + ["/my/class.rb", "/my/module.rb"], + @bc.clean(["/my/prefix/my/class.rb", "/my/prefix/my/module.rb"]) end test "backtrace cleaner should allow removing filters" do diff --git a/activesupport/test/core_ext/date_and_time_behavior.rb b/activesupport/test/core_ext/date_and_time_behavior.rb index 256353c309..42da6f6cd0 100644 --- a/activesupport/test/core_ext/date_and_time_behavior.rb +++ b/activesupport/test/core_ext/date_and_time_behavior.rb @@ -9,6 +9,11 @@ module DateAndTimeBehavior end def test_prev_day + assert_equal date_time_init(2005, 2, 24, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_day(-2) + assert_equal date_time_init(2005, 2, 23, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_day(-1) + assert_equal date_time_init(2005, 2, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_day(0) + assert_equal date_time_init(2005, 2, 21, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_day(1) + assert_equal date_time_init(2005, 2, 20, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_day(2) assert_equal date_time_init(2005, 2, 21, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_day assert_equal date_time_init(2005, 2, 28, 10, 10, 10), date_time_init(2005, 3, 2, 10, 10, 10).prev_day.prev_day end @@ -19,6 +24,11 @@ module DateAndTimeBehavior end def test_next_day + assert_equal date_time_init(2005, 2, 20, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_day(-2) + assert_equal date_time_init(2005, 2, 21, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_day(-1) + assert_equal date_time_init(2005, 2, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_day(0) + assert_equal date_time_init(2005, 2, 23, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_day(1) + assert_equal date_time_init(2005, 2, 24, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_day(2) assert_equal date_time_init(2005, 2, 23, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_day assert_equal date_time_init(2005, 3, 2, 10, 10, 10), date_time_init(2005, 2, 28, 10, 10, 10).next_day.next_day end @@ -151,6 +161,16 @@ module DateAndTimeBehavior assert_equal date_time_init(2015, 1, 5, 15, 15, 10), date_time_init(2015, 1, 3, 15, 15, 10).next_weekday end + def test_next_month + assert_equal date_time_init(2004, 12, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_month(-2) + assert_equal date_time_init(2005, 1, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_month(-1) + assert_equal date_time_init(2005, 2, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_month(0) + assert_equal date_time_init(2005, 3, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_month(1) + assert_equal date_time_init(2005, 4, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_month(2) + assert_equal date_time_init(2005, 3, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_month + assert_equal date_time_init(2005, 4, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_month.next_month + end + def test_next_month_on_31st assert_equal date_time_init(2005, 9, 30, 15, 15, 10), date_time_init(2005, 8, 31, 15, 15, 10).next_month end @@ -160,7 +180,13 @@ module DateAndTimeBehavior end def test_next_year + assert_equal date_time_init(2003, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).next_year(-2) + assert_equal date_time_init(2004, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).next_year(-1) + assert_equal date_time_init(2005, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).next_year(0) + assert_equal date_time_init(2006, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).next_year(1) + assert_equal date_time_init(2007, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).next_year(2) assert_equal date_time_init(2006, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).next_year + assert_equal date_time_init(2007, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).next_year.next_year end def test_prev_week @@ -203,6 +229,16 @@ module DateAndTimeBehavior assert_equal date_time_init(2015, 1, 2, 15, 15, 10), date_time_init(2015, 1, 4, 15, 15, 10).prev_weekday end + def test_prev_month + assert_equal date_time_init(2005, 4, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_month(-2) + assert_equal date_time_init(2005, 3, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_month(-1) + assert_equal date_time_init(2005, 2, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_month(0) + assert_equal date_time_init(2005, 1, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_month(1) + assert_equal date_time_init(2004, 12, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_month(2) + assert_equal date_time_init(2005, 1, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_month + assert_equal date_time_init(2004, 12, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_month.prev_month + end + def test_prev_month_on_31st assert_equal date_time_init(2004, 2, 29, 10, 10, 10), date_time_init(2004, 3, 31, 10, 10, 10).prev_month end @@ -212,7 +248,21 @@ module DateAndTimeBehavior end def test_prev_year + assert_equal date_time_init(2007, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).prev_year(-2) + assert_equal date_time_init(2006, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).prev_year(-1) + assert_equal date_time_init(2005, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).prev_year(0) + assert_equal date_time_init(2004, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).prev_year(1) + assert_equal date_time_init(2003, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).prev_year(2) assert_equal date_time_init(2004, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).prev_year + assert_equal date_time_init(2003, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).prev_year.prev_year + end + + def test_last_month_on_31st + assert_equal date_time_init(2004, 2, 29, 0, 0, 0), date_time_init(2004, 3, 31, 0, 0, 0).last_month + end + + def test_last_year + assert_equal date_time_init(2004, 6, 5, 10, 0, 0), date_time_init(2005, 6, 5, 10, 0, 0).last_year end def test_days_to_week_start diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb index b8672eac4b..0c6f3f595a 100644 --- a/activesupport/test/core_ext/date_ext_test.rb +++ b/activesupport/test/core_ext/date_ext_test.rb @@ -120,10 +120,6 @@ class DateExtCalculationsTest < ActiveSupport::TestCase assert_equal Date.new(1582, 10, 4), Date.new(1583, 10, 14).prev_year end - def test_last_year - assert_equal Date.new(2004, 6, 5), Date.new(2005, 6, 5).last_year - end - def test_last_year_in_leap_years assert_equal Date.new(1999, 2, 28), Date.new(2000, 2, 29).last_year end @@ -185,10 +181,6 @@ class DateExtCalculationsTest < ActiveSupport::TestCase assert_equal Date.new(1582, 10, 18), Date.new(1582, 10, 4).next_week end - def test_last_month_on_31st - assert_equal Date.new(2004, 2, 29), Date.new(2004, 3, 31).last_month - end - def test_last_quarter_on_31st assert_equal Date.new(2004, 2, 29), Date.new(2004, 5, 31).last_quarter end diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb index a3c2018a31..d942cddb2a 100644 --- a/activesupport/test/core_ext/date_time_ext_test.rb +++ b/activesupport/test/core_ext/date_time_ext_test.rb @@ -162,10 +162,6 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase assert_equal DateTime.civil(2005, 4, 30, 23, 59, Rational(59999999999, 1000000000)), DateTime.civil(2005, 4, 20, 10, 10, 10).end_of_month end - def test_last_year - assert_equal DateTime.civil(2004, 6, 5, 10), DateTime.civil(2005, 6, 5, 10, 0, 0).last_year - end - def test_ago assert_equal DateTime.civil(2005, 2, 22, 10, 10, 9), DateTime.civil(2005, 2, 22, 10, 10, 10).ago(1) assert_equal DateTime.civil(2005, 2, 22, 9, 10, 10), DateTime.civil(2005, 2, 22, 10, 10, 10).ago(3600) @@ -248,10 +244,6 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase assert_equal DateTime.civil(2016, 2, 29), DateTime.civil(2016, 3, 7).last_week end - def test_last_month_on_31st - assert_equal DateTime.civil(2004, 2, 29), DateTime.civil(2004, 3, 31).last_month - end - def test_last_quarter_on_31st assert_equal DateTime.civil(2004, 2, 29), DateTime.civil(2004, 5, 31).last_quarter end diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb index c655f466f2..b3e0cd8bd0 100644 --- a/activesupport/test/core_ext/duration_test.rb +++ b/activesupport/test/core_ext/duration_test.rb @@ -111,7 +111,44 @@ class DurationTest < ActiveSupport::TestCase def test_divide assert_equal 1.day, 7.days / 7 assert_instance_of ActiveSupport::Duration, 7.days / 7 + + assert_equal 1.hour, 1.day / 24 + assert_instance_of ActiveSupport::Duration, 1.day / 24 + + assert_equal 24, 86400 / 1.hour + assert_kind_of Integer, 86400 / 1.hour + + assert_equal 24, 1.day / 1.hour + assert_kind_of Integer, 1.day / 1.hour + assert_equal 1, 1.day / 1.day + assert_kind_of Integer, 1.day / 1.hour + end + + def test_modulo + assert_equal 1.minute, 5.minutes % 120 + assert_instance_of ActiveSupport::Duration, 5.minutes % 120 + + assert_equal 1.minute, 5.minutes % 2.minutes + assert_instance_of ActiveSupport::Duration, 5.minutes % 2.minutes + + assert_equal 1.minute, 5.minutes % 120.seconds + assert_instance_of ActiveSupport::Duration, 5.minutes % 120.seconds + + assert_equal 5.minutes, 5.minutes % 1.hour + assert_instance_of ActiveSupport::Duration, 5.minutes % 1.hour + + assert_equal 1.day, 36.days % 604800 + assert_instance_of ActiveSupport::Duration, 36.days % 604800 + + assert_equal 1.day, 36.days % 7.days + assert_instance_of ActiveSupport::Duration, 36.days % 7.days + + assert_equal 800.seconds, 8000 % 1.hour + assert_instance_of ActiveSupport::Duration, 8000 % 1.hour + + assert_equal 1.month, 13.months % 1.year + assert_instance_of ActiveSupport::Duration, 13.months % 1.year end def test_date_added_with_multiplied_duration @@ -410,9 +447,9 @@ class DurationTest < ActiveSupport::TestCase assert_equal 5, scalar / 2 assert_instance_of ActiveSupport::Duration::Scalar, scalar / 2 assert_equal 10, 100.seconds / scalar - assert_instance_of ActiveSupport::Duration, 2.seconds * scalar + assert_instance_of ActiveSupport::Duration, 100.seconds / scalar assert_equal 5, scalar / 2.seconds - assert_instance_of ActiveSupport::Duration, scalar / 2.seconds + assert_kind_of Integer, scalar / 2.seconds exception = assert_raises(TypeError) do scalar / "foo" @@ -421,13 +458,29 @@ class DurationTest < ActiveSupport::TestCase assert_equal "no implicit conversion of String into ActiveSupport::Duration::Scalar", exception.message end - def test_scalar_divide_parts + def test_scalar_modulo scalar = ActiveSupport::Duration::Scalar.new(10) - assert_equal({ days: 2 }, (scalar / 5.days).parts) - assert_equal(172800, (scalar / 5.days).value) - assert_equal({ days: -2 }, (scalar / -5.days).parts) - assert_equal(-172800, (scalar / -5.days).value) + assert_equal 1, 31 % scalar + assert_instance_of ActiveSupport::Duration::Scalar, 31 % scalar + assert_equal 1, scalar % 3 + assert_instance_of ActiveSupport::Duration::Scalar, scalar % 3 + assert_equal 1, 31.seconds % scalar + assert_instance_of ActiveSupport::Duration, 31.seconds % scalar + assert_equal 1, scalar % 3.seconds + assert_instance_of ActiveSupport::Duration, scalar % 3.seconds + + exception = assert_raises(TypeError) do + scalar % "foo" + end + + assert_equal "no implicit conversion of String into ActiveSupport::Duration::Scalar", exception.message + end + + def test_scalar_modulo_parts + scalar = ActiveSupport::Duration::Scalar.new(82800) + assert_equal({ hours: 1 }, (scalar % 2.hours).parts) + assert_equal(3600, (scalar % 2.hours).value) end def test_twelve_months_equals_one_year diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index c537fb86fe..746d7ad416 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -302,9 +302,9 @@ class HashExtTest < ActiveSupport::TestCase end def test_reverse_merge - defaults = { a: "x", b: "y", c: 10 }.freeze + defaults = { d: 0, a: "x", b: "y", c: 10 }.freeze options = { a: 1, b: 2 } - expected = { a: 1, b: 2, c: 10 } + expected = { d: 0, a: 1, b: 2, c: 10 } # Should merge defaults into options, creating a new hash. assert_equal expected, options.reverse_merge(defaults) @@ -315,6 +315,9 @@ class HashExtTest < ActiveSupport::TestCase assert_equal expected, merged.reverse_merge!(defaults) assert_equal expected, merged + # Make the order consistent with the non-overwriting reverse merge. + assert_equal expected.keys, merged.keys + # Should be an alias for reverse_merge! merged = options.dup assert_equal expected, merged.reverse_update(defaults) diff --git a/activesupport/test/core_ext/load_error_test.rb b/activesupport/test/core_ext/load_error_test.rb index 4fdd228ff8..41b11d0c33 100644 --- a/activesupport/test/core_ext/load_error_test.rb +++ b/activesupport/test/core_ext/load_error_test.rb @@ -5,7 +5,7 @@ require "active_support/core_ext/load_error" class TestLoadError < ActiveSupport::TestCase def test_with_require - assert_raise(LoadError) { require 'no_this_file_don\'t_exist' } + assert_raise(LoadError) { require "no_this_file_don't_exist" } end def test_with_load assert_raise(LoadError) { load "nor_does_this_one" } diff --git a/activesupport/test/core_ext/module/reachable_test.rb b/activesupport/test/core_ext/module/reachable_test.rb index a69fc6839e..097a72fa5b 100644 --- a/activesupport/test/core_ext/module/reachable_test.rb +++ b/activesupport/test/core_ext/module/reachable_test.rb @@ -5,13 +5,17 @@ require "active_support/core_ext/module/reachable" class AnonymousTest < ActiveSupport::TestCase test "an anonymous class or module is not reachable" do - assert !Module.new.reachable? - assert !Class.new.reachable? + assert_deprecated do + assert !Module.new.reachable? + assert !Class.new.reachable? + end end test "ordinary named classes or modules are reachable" do - assert Kernel.reachable? - assert Object.reachable? + assert_deprecated do + assert Kernel.reachable? + assert Object.reachable? + end end test "a named class or module whose constant has gone is not reachable" do @@ -21,8 +25,10 @@ class AnonymousTest < ActiveSupport::TestCase self.class.send(:remove_const, :C) self.class.send(:remove_const, :M) - assert !c.reachable? - assert !m.reachable? + assert_deprecated do + assert !c.reachable? + assert !m.reachable? + end end test "a named class or module whose constants store different objects are not reachable" do @@ -35,9 +41,11 @@ class AnonymousTest < ActiveSupport::TestCase eval "class C; end" eval "module M; end" - assert C.reachable? - assert M.reachable? - assert !c.reachable? - assert !m.reachable? + assert_deprecated do + assert C.reachable? + assert M.reachable? + assert !c.reachable? + assert !m.reachable? + end end end diff --git a/activesupport/test/core_ext/module/remove_method_test.rb b/activesupport/test/core_ext/module/remove_method_test.rb index dbf71b477d..8493be8d08 100644 --- a/activesupport/test/core_ext/module/remove_method_test.rb +++ b/activesupport/test/core_ext/module/remove_method_test.rb @@ -6,22 +6,22 @@ require "active_support/core_ext/module/remove_method" module RemoveMethodTests class A def do_something - return 1 + 1 end def do_something_protected - return 1 + 1 end protected :do_something_protected def do_something_private - return 1 + 1 end private :do_something_private class << self def do_something_else - return 2 + 2 end end end diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb index 69c8a312d8..e918823074 100644 --- a/activesupport/test/core_ext/module_test.rb +++ b/activesupport/test/core_ext/module_test.rb @@ -374,6 +374,14 @@ class ModuleTest < ActiveSupport::TestCase assert_match(/undefined method `my_fake_method' for/, e.message) end + def test_delegate_missing_to_raises_delegation_error_if_target_nil + e = assert_raises(Module::DelegationError) do + DecoratedTester.new(nil).name + end + + assert_equal "name delegated to client, but client is nil", e.message + end + def test_delegate_missing_to_affects_respond_to assert DecoratedTester.new(@david).respond_to?(:name) assert_not DecoratedTester.new(@david).respond_to?(:private_name) diff --git a/activesupport/test/core_ext/object/instance_variables_test.rb b/activesupport/test/core_ext/object/instance_variables_test.rb index b9ec827954..a3d8daab5b 100644 --- a/activesupport/test/core_ext/object/instance_variables_test.rb +++ b/activesupport/test/core_ext/object/instance_variables_test.rb @@ -19,7 +19,7 @@ class ObjectInstanceVariableTest < ActiveSupport::TestCase end def test_instance_exec_passes_arguments_to_block - assert_equal %w(hello goodbye), "hello".instance_exec("goodbye") { |v| [self, v] } + assert_equal %w(hello goodbye), "hello".dup.instance_exec("goodbye") { |v| [self, v] } end def test_instance_exec_with_frozen_obj @@ -27,7 +27,7 @@ class ObjectInstanceVariableTest < ActiveSupport::TestCase end def test_instance_exec_nested - assert_equal %w(goodbye olleh bar), "hello".instance_exec("goodbye") { |arg| + assert_equal %w(goodbye olleh bar), "hello".dup.instance_exec("goodbye") { |arg| [arg] + instance_exec("bar") { |v| [reverse, v] } } end end diff --git a/activesupport/test/core_ext/range_ext_test.rb b/activesupport/test/core_ext/range_ext_test.rb index a96e3d62e8..049fac8fd4 100644 --- a/activesupport/test/core_ext/range_ext_test.rb +++ b/activesupport/test/core_ext/range_ext_test.rb @@ -16,6 +16,11 @@ class RangeTest < ActiveSupport::TestCase assert_equal "BETWEEN '2005-12-10 15:30:00' AND '2005-12-10 17:30:00'", date_range.to_s(:db) end + def test_to_s_with_alphabets + alphabet_range = ("a".."z") + assert_equal "BETWEEN 'a' AND 'z'", alphabet_range.to_s(:db) + end + def test_to_s_with_numeric number_range = (1..100) assert_equal "BETWEEN '1' AND '100'", number_range.to_s(:db) @@ -116,9 +121,12 @@ class RangeTest < ActiveSupport::TestCase def test_include_on_time_with_zone twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Eastern Time (US & Canada)"] , Time.utc(2006, 11, 28, 10, 30)) - assert_raises TypeError do - ((twz - 1.hour)..twz).include?(twz) - end + assert ((twz - 1.hour)..twz).include?(twz) + end + + def test_case_equals_on_time_with_zone + twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Eastern Time (US & Canada)"] , Time.utc(2006, 11, 28, 10, 30)) + assert ((twz - 1.hour)..twz) === twz end def test_date_time_with_each diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index 0afff5aa50..5c5abe9fd1 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -108,6 +108,13 @@ class StringInflectionsTest < ActiveSupport::TestCase assert_equal("capital", "Capital".camelize(:lower)) end + def test_camelize_invalid_option + e = assert_raise ArgumentError do + "Capital".camelize(nil) + end + assert_equal("Invalid option, use either :upper or :lower.", e.message) + end + def test_dasherize UnderscoresToDashes.each do |underscored, dasherized| assert_equal(dasherized, underscored.dasherize) @@ -190,7 +197,7 @@ class StringInflectionsTest < ActiveSupport::TestCase end def test_string_parameterized_underscore_preserve_case - StringToParameterizePreserceCaseWithUnderscore.each do |normal, slugged| + StringToParameterizePreserveCaseWithUnderscore.each do |normal, slugged| assert_equal(slugged, normal.parameterize(separator: "_", preserve_case: true)) end end diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb index dc2f4c5ac7..8cb17df01b 100644 --- a/activesupport/test/core_ext/time_ext_test.rb +++ b/activesupport/test/core_ext/time_ext_test.rb @@ -178,10 +178,6 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase assert_equal Time.local(2005, 2, 4, 19, 30, 59, Rational(999999999, 1000)), Time.local(2005, 2, 4, 19, 30, 10).end_of_minute end - def test_last_year - assert_equal Time.local(2004, 6, 5, 10), Time.local(2005, 6, 5, 10, 0, 0).last_year - end - def test_ago assert_equal Time.local(2005, 2, 22, 10, 10, 9), Time.local(2005, 2, 22, 10, 10, 10).ago(1) assert_equal Time.local(2005, 2, 22, 9, 10, 10), Time.local(2005, 2, 22, 10, 10, 10).ago(3600) @@ -664,10 +660,6 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase end end - def test_last_month_on_31st - assert_equal Time.local(2004, 2, 29), Time.local(2004, 3, 31).last_month - end - def test_xmlschema_is_available assert_nothing_raised { Time.now.xmlschema } end diff --git a/activesupport/test/current_attributes_test.rb b/activesupport/test/current_attributes_test.rb index d333ffc8f5..1669f08f68 100644 --- a/activesupport/test/current_attributes_test.rb +++ b/activesupport/test/current_attributes_test.rb @@ -30,7 +30,14 @@ class CurrentAttributesTest < ActiveSupport::TestCase end end - setup { Current.reset } + setup do + @original_time_zone = Time.zone + Current.reset + end + + teardown do + Time.zone = @original_time_zone + end test "read and write attribute" do Current.world = "world/1" diff --git a/activesupport/test/encrypted_configuration_test.rb b/activesupport/test/encrypted_configuration_test.rb new file mode 100644 index 0000000000..0bc915be82 --- /dev/null +++ b/activesupport/test/encrypted_configuration_test.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require "abstract_unit" +require "active_support/encrypted_configuration" + +class EncryptedConfigurationTest < ActiveSupport::TestCase + setup do + @credentials_config_path = File.join(Dir.tmpdir, "credentials.yml.enc") + + @credentials_key_path = File.join(Dir.tmpdir, "master.key") + File.write(@credentials_key_path, ActiveSupport::EncryptedConfiguration.generate_key) + + @credentials = ActiveSupport::EncryptedConfiguration.new \ + config_path: @credentials_config_path, key_path: @credentials_key_path, env_key: "RAILS_MASTER_KEY" + end + + teardown do + FileUtils.rm_rf @credentials_config_path + FileUtils.rm_rf @credentials_key_path + end + + test "reading configuration by env key" do + FileUtils.rm_rf @credentials_key_path + + begin + ENV["RAILS_MASTER_KEY"] = ActiveSupport::EncryptedConfiguration.generate_key + @credentials.write({ something: { good: true, bad: false } }.to_yaml) + + assert @credentials[:something][:good] + assert_not @credentials.dig(:something, :bad) + assert_nil @credentials.fetch(:nothing, nil) + ensure + ENV["RAILS_MASTER_KEY"] = nil + end + end + + test "reading configuration by key file" do + @credentials.write({ something: { good: true } }.to_yaml) + + assert @credentials.something[:good] + end + + test "change configuration by key file" do + @credentials.write({ something: { good: true } }.to_yaml) + @credentials.change do |config_file| + config = YAML.load(config_file.read) + config_file.write config.merge(new: "things").to_yaml + end + + assert @credentials.something[:good] + assert_equal "things", @credentials[:new] + end + + test "raise error when writing an invalid format value" do + assert_raise(Psych::SyntaxError) do + @credentials.change do |config_file| + config_file.write "login: *login\n username: dummy" + end + end + end + + test "raises key error when accessing config via bang method" do + assert_raise(KeyError) { @credentials.something! } + end +end diff --git a/activesupport/test/encrypted_file_test.rb b/activesupport/test/encrypted_file_test.rb new file mode 100644 index 0000000000..7259726d08 --- /dev/null +++ b/activesupport/test/encrypted_file_test.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require "abstract_unit" +require "active_support/encrypted_file" + +class EncryptedFileTest < ActiveSupport::TestCase + setup do + @content = "One little fox jumped over the hedge" + + @content_path = File.join(Dir.tmpdir, "content.txt.enc") + + @key_path = File.join(Dir.tmpdir, "content.txt.key") + File.write(@key_path, ActiveSupport::EncryptedFile.generate_key) + + @encrypted_file = ActiveSupport::EncryptedFile.new \ + content_path: @content_path, key_path: @key_path, env_key: "CONTENT_KEY" + end + + teardown do + FileUtils.rm_rf @content_path + FileUtils.rm_rf @key_path + end + + test "reading content by env key" do + FileUtils.rm_rf @key_path + + begin + ENV["CONTENT_KEY"] = ActiveSupport::EncryptedFile.generate_key + @encrypted_file.write @content + + assert_equal @content, @encrypted_file.read + ensure + ENV["CONTENT_KEY"] = nil + end + end + + test "reading content by key file" do + @encrypted_file.write(@content) + assert_equal @content, @encrypted_file.read + end + + test "change content by key file" do + @encrypted_file.write(@content) + @encrypted_file.change do |file| + file.write(file.read + " and went by the lake") + end + + assert_equal "#{@content} and went by the lake", @encrypted_file.read + end +end diff --git a/activesupport/test/hash_with_indifferent_access_test.rb b/activesupport/test/hash_with_indifferent_access_test.rb index 4acdd10de6..41d653fa59 100644 --- a/activesupport/test/hash_with_indifferent_access_test.rb +++ b/activesupport/test/hash_with_indifferent_access_test.rb @@ -399,6 +399,36 @@ class HashWithIndifferentAccessTest < ActiveSupport::TestCase assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings end + def test_indifferent_transform_keys + hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).transform_keys { |k| k * 2 } + + assert_equal({ "aa" => 1, "bb" => 2 }, hash) + assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash + end + + def test_indifferent_transform_keys_bang + indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings) + indifferent_strings.transform_keys! { |k| k * 2 } + + assert_equal({ "aa" => 1, "bb" => 2 }, indifferent_strings) + assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings + end + + def test_indifferent_transform_values + hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).transform_values { |v| v * 2 } + + assert_equal({ "a" => 2, "b" => 4 }, hash) + assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash + end + + def test_indifferent_transform_values_bang + indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings) + indifferent_strings.transform_values! { |v| v * 2 } + + assert_equal({ "a" => 2, "b" => 4 }, indifferent_strings) + assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings + end + def test_indifferent_compact hash_contain_nil_value = @strings.merge("z" => nil) hash = ActiveSupport::HashWithIndifferentAccess.new(hash_contain_nil_value) @@ -537,6 +567,32 @@ class HashWithIndifferentAccessTest < ActiveSupport::TestCase assert_equal 1234, data.dig(:this, :views) end + def test_argless_default_with_existing_nil_key + h = Hash.new(:default).merge(nil => "defined").with_indifferent_access + + assert_equal :default, h.default + end + + def test_default_with_argument + h = Hash.new { 5 }.merge(1 => 2).with_indifferent_access + + assert_equal 5, h.default(1) + end + + def test_default_proc + h = ActiveSupport::HashWithIndifferentAccess.new { |hash, key| key } + + assert_nil h.default + assert_equal "foo", h.default("foo") + assert_equal "foo", h.default(:foo) + end + + def test_double_conversion_with_nil_key + h = { nil => "defined" }.with_indifferent_access.with_indifferent_access + + assert_nil h[:undefined_key] + end + def test_assorted_keys_not_stringified original = { Object.new => 2, 1 => 2, [] => true } indiff = original.with_indifferent_access diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb index eeec0ab1a5..0e3e576a70 100644 --- a/activesupport/test/inflector_test.rb +++ b/activesupport/test/inflector_test.rb @@ -224,6 +224,12 @@ class InflectorTest < ActiveSupport::TestCase assert_equal("json_html_api", ActiveSupport::Inflector.underscore("JSONHTMLAPI")) end + def test_acronym_regexp_is_deprecated + assert_deprecated do + ActiveSupport::Inflector.inflections.acronym_regex + end + end + def test_underscore CamelToUnderscore.each do |camel, underscore| assert_equal(underscore, ActiveSupport::Inflector.underscore(camel)) @@ -356,6 +362,19 @@ class InflectorTest < ActiveSupport::TestCase assert_equal("Col rpted bugs", ActiveSupport::Inflector.humanize("COL_rpted_bugs")) end + def test_humanize_with_acronyms + ActiveSupport::Inflector.inflections do |inflect| + inflect.acronym "LAX" + inflect.acronym "SFO" + end + assert_equal("LAX roundtrip to SFO", ActiveSupport::Inflector.humanize("LAX ROUNDTRIP TO SFO")) + assert_equal("LAX roundtrip to SFO", ActiveSupport::Inflector.humanize("LAX ROUNDTRIP TO SFO", capitalize: false)) + assert_equal("LAX roundtrip to SFO", ActiveSupport::Inflector.humanize("lax roundtrip to sfo")) + assert_equal("LAX roundtrip to SFO", ActiveSupport::Inflector.humanize("lax roundtrip to sfo", capitalize: false)) + assert_equal("LAX roundtrip to SFO", ActiveSupport::Inflector.humanize("Lax Roundtrip To Sfo")) + assert_equal("LAX roundtrip to SFO", ActiveSupport::Inflector.humanize("Lax Roundtrip To Sfo", capitalize: false)) + end + def test_constantize run_constantize_tests_on do |string| ActiveSupport::Inflector.constantize(string) diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb index f1214671ce..689370cccf 100644 --- a/activesupport/test/inflector_test_cases.rb +++ b/activesupport/test/inflector_test_cases.rb @@ -221,7 +221,7 @@ module InflectorTestCases "Test with malformed utf8 \251" => "test_with_malformed_utf8" } - StringToParameterizePreserceCaseWithUnderscore = { + StringToParameterizePreserveCaseWithUnderscore = { "Donald E. Knuth" => "Donald_E_Knuth", "Random text with *(bad)* characters" => "Random_text_with_bad_characters", "With-some-dashes" => "With-some-dashes", diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb index 80d03ec9d6..96ad8dfbdb 100644 --- a/activesupport/test/json/encoding_test.rb +++ b/activesupport/test/json/encoding_test.rb @@ -100,11 +100,11 @@ class TestJSONEncoding < ActiveSupport::TestCase end def test_hash_should_allow_key_filtering_with_only - assert_equal %({"a":1}), ActiveSupport::JSON.encode({ "a" => 1, :b => 2, :c => 3 }, only: "a") + assert_equal %({"a":1}), ActiveSupport::JSON.encode({ "a" => 1, :b => 2, :c => 3 }, { only: "a" }) end def test_hash_should_allow_key_filtering_with_except - assert_equal %({"b":2}), ActiveSupport::JSON.encode({ "foo" => "bar", :b => 2, :c => 3 }, except: ["foo", :c]) + assert_equal %({"b":2}), ActiveSupport::JSON.encode({ "foo" => "bar", :b => 2, :c => 3 }, { except: ["foo", :c] }) end def test_time_to_json_includes_local_offset @@ -454,6 +454,10 @@ EXPECTED assert_equal '{"number":null}', NaNNumber.new.to_json end + def test_to_json_works_on_io_objects + assert_equal STDOUT.to_s.to_json, STDOUT.to_json + end + private def object_keys(json_object) diff --git a/activesupport/test/lazy_load_hooks_test.rb b/activesupport/test/lazy_load_hooks_test.rb index 9c9264e8fc..721d44d0c1 100644 --- a/activesupport/test/lazy_load_hooks_test.rb +++ b/activesupport/test/lazy_load_hooks_test.rb @@ -20,6 +20,23 @@ class LazyLoadHooksTest < ActiveSupport::TestCase assert_equal 7, i end + def test_basic_hook_with_two_registrations_only_once + i = 0 + block = proc { i += incr } + ActiveSupport.on_load(:basic_hook_with_two_once, run_once: true, &block) + ActiveSupport.on_load(:basic_hook_with_two_once) do + i += incr + end + + ActiveSupport.on_load(:different_hook, run_once: true, &block) + ActiveSupport.run_load_hooks(:different_hook, FakeContext.new(2)) + assert_equal 2, i + ActiveSupport.run_load_hooks(:basic_hook_with_two_once, FakeContext.new(2)) + assert_equal 6, i + ActiveSupport.run_load_hooks(:basic_hook_with_two_once, FakeContext.new(5)) + assert_equal 11, i + end + def test_hook_registered_after_run i = 0 ActiveSupport.run_load_hooks(:registered_after) @@ -37,6 +54,15 @@ class LazyLoadHooksTest < ActiveSupport::TestCase assert_equal 7, i end + def test_hook_registered_after_run_with_two_registrations_only_once + i = 0 + ActiveSupport.run_load_hooks(:registered_after_with_two_once, FakeContext.new(2)) + ActiveSupport.run_load_hooks(:registered_after_with_two_once, FakeContext.new(5)) + assert_equal 0, i + ActiveSupport.on_load(:registered_after_with_two_once, run_once: true) { i += incr } + assert_equal 2, i + end + def test_hook_registered_interleaved_run_with_two_registrations i = 0 ActiveSupport.run_load_hooks(:registered_interleaved_with_two, FakeContext.new(2)) @@ -47,6 +73,22 @@ class LazyLoadHooksTest < ActiveSupport::TestCase assert_equal 7, i end + def test_hook_registered_interleaved_run_with_two_registrations_once + i = 0 + ActiveSupport + .run_load_hooks(:registered_interleaved_with_two_once, FakeContext.new(2)) + assert_equal 0, i + + ActiveSupport.on_load(:registered_interleaved_with_two_once, run_once: true) do + i += incr + end + assert_equal 2, i + + ActiveSupport + .run_load_hooks(:registered_interleaved_with_two_once, FakeContext.new(5)) + assert_equal 2, i + end + def test_hook_receives_a_context i = 0 ActiveSupport.on_load(:contextual) { i += incr } diff --git a/activesupport/test/message_encryptor_test.rb b/activesupport/test/message_encryptor_test.rb index 832841597f..9edf07f762 100644 --- a/activesupport/test/message_encryptor_test.rb +++ b/activesupport/test/message_encryptor_test.rb @@ -4,6 +4,7 @@ require "abstract_unit" require "openssl" require "active_support/time" require "active_support/json" +require_relative "metadata/shared_metadata_tests" class MessageEncryptorTest < ActiveSupport::TestCase class JSONSerializer @@ -106,8 +107,81 @@ class MessageEncryptorTest < ActiveSupport::TestCase assert_aead_not_decrypted(encryptor, [text, iv, auth_tag[0..-2]] * "--") end - private + def test_backwards_compatibility_decrypt_previously_encrypted_messages_without_metadata + secret = "\xB7\xF0\xBCW\xB1\x18`\xAB\xF0\x81\x10\xA4$\xF44\xEC\xA1\xDC\xC1\xDDD\xAF\xA9\xB8\x14\xCD\x18\x9A\x99 \x80)" + encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: "aes-256-gcm") + encrypted_message = "9cVnFs2O3lL9SPvIJuxBOLS51nDiBMw=--YNI5HAfHEmZ7VDpl--ddFJ6tXA0iH+XGcCgMINYQ==" + + assert_equal "Ruby on Rails", encryptor.decrypt_and_verify(encrypted_message) + end + + def test_rotating_secret + old_message = ActiveSupport::MessageEncryptor.new(secrets[:old], cipher: "aes-256-gcm").encrypt_and_sign("old") + + encryptor = ActiveSupport::MessageEncryptor.new(@secret, cipher: "aes-256-gcm") + encryptor.rotate secrets[:old] + + assert_equal "old", encryptor.decrypt_and_verify(old_message) + end + + def test_rotating_serializer + old_message = ActiveSupport::MessageEncryptor.new(secrets[:old], cipher: "aes-256-gcm", serializer: JSON). + encrypt_and_sign(ahoy: :hoy) + + encryptor = ActiveSupport::MessageEncryptor.new(@secret, cipher: "aes-256-gcm", serializer: JSON) + encryptor.rotate secrets[:old] + + assert_equal({ "ahoy" => "hoy" }, encryptor.decrypt_and_verify(old_message)) + end + + def test_rotating_aes_cbc_secrets + old_encryptor = ActiveSupport::MessageEncryptor.new(secrets[:old], "old sign", cipher: "aes-256-cbc") + old_message = old_encryptor.encrypt_and_sign("old") + + encryptor = ActiveSupport::MessageEncryptor.new(@secret) + encryptor.rotate secrets[:old], "old sign", cipher: "aes-256-cbc" + + assert_equal "old", encryptor.decrypt_and_verify(old_message) + end + + def test_multiple_rotations + older_message = ActiveSupport::MessageEncryptor.new(secrets[:older], "older sign").encrypt_and_sign("older") + old_message = ActiveSupport::MessageEncryptor.new(secrets[:old], "old sign").encrypt_and_sign("old") + + encryptor = ActiveSupport::MessageEncryptor.new(@secret) + encryptor.rotate secrets[:old], "old sign" + encryptor.rotate secrets[:older], "older sign" + + assert_equal "new", encryptor.decrypt_and_verify(encryptor.encrypt_and_sign("new")) + assert_equal "old", encryptor.decrypt_and_verify(old_message) + assert_equal "older", encryptor.decrypt_and_verify(older_message) + end + + def test_on_rotation_is_called_and_returns_modified_messages + older_message = ActiveSupport::MessageEncryptor.new(secrets[:older], "older sign").encrypt_and_sign(encoded: "message") + + encryptor = ActiveSupport::MessageEncryptor.new(@secret) + encryptor.rotate secrets[:old] + encryptor.rotate secrets[:older], "older sign" + + rotated = false + message = encryptor.decrypt_and_verify(older_message, on_rotation: proc { rotated = true }) + + assert_equal({ encoded: "message" }, message) + assert rotated + end + + def test_with_rotated_metadata + old_message = ActiveSupport::MessageEncryptor.new(secrets[:old], cipher: "aes-256-gcm"). + encrypt_and_sign("metadata", purpose: :rotation) + encryptor = ActiveSupport::MessageEncryptor.new(@secret, cipher: "aes-256-gcm") + encryptor.rotate secrets[:old] + + assert_equal "metadata", encryptor.decrypt_and_verify(old_message, purpose: :rotation) + end + + private def assert_aead_not_decrypted(encryptor, value) assert_raise(ActiveSupport::MessageEncryptor::InvalidMessage) do encryptor.decrypt_and_verify(value) @@ -126,9 +200,47 @@ class MessageEncryptorTest < ActiveSupport::TestCase end end + def secrets + @secrets ||= Hash.new { |h, k| h[k] = SecureRandom.random_bytes(32) } + end + def munge(base64_string) bits = ::Base64.strict_decode64(base64_string) bits.reverse! ::Base64.strict_encode64(bits) end end + +class MessageEncryptorMetadataTest < ActiveSupport::TestCase + include SharedMessageMetadataTests + + setup do + @secret = SecureRandom.random_bytes(32) + @encryptor = ActiveSupport::MessageEncryptor.new(@secret, encryptor_options) + end + + private + def generate(message, **options) + @encryptor.encrypt_and_sign(message, options) + end + + def parse(data, **options) + @encryptor.decrypt_and_verify(data, options) + end + + def encryptor_options; end +end + +class MessageEncryptorMetadataMarshalTest < MessageEncryptorMetadataTest + private + def encryptor_options + { serializer: Marshal } + end +end + +class MessageEncryptorMetadataJSONTest < MessageEncryptorMetadataTest + private + def encryptor_options + { serializer: MessageEncryptorTest::JSONSerializer.new } + end +end diff --git a/activesupport/test/message_verifier_test.rb b/activesupport/test/message_verifier_test.rb index ba886f9fb4..05d5c1cbc3 100644 --- a/activesupport/test/message_verifier_test.rb +++ b/activesupport/test/message_verifier_test.rb @@ -4,6 +4,7 @@ require "abstract_unit" require "openssl" require "active_support/time" require "active_support/json" +require_relative "metadata/shared_metadata_tests" class MessageVerifierTest < ActiveSupport::TestCase class JSONSerializer @@ -18,7 +19,8 @@ class MessageVerifierTest < ActiveSupport::TestCase def setup @verifier = ActiveSupport::MessageVerifier.new("Hey, I'm a secret!") - @data = { some: "data", now: Time.local(2010) } + @data = { some: "data", now: Time.utc(2010) } + @secret = SecureRandom.random_bytes(32) end def test_valid_message @@ -84,4 +86,119 @@ class MessageVerifierTest < ActiveSupport::TestCase end assert_equal "Secret should not be nil.", exception.message end + + def test_backward_compatibility_messages_signed_without_metadata + signed_message = "BAh7BzoJc29tZUkiCWRhdGEGOgZFVDoIbm93SXU6CVRpbWUNIIAbgAAAAAAHOgtvZmZzZXRpADoJem9uZUkiCFVUQwY7BkY=--d03c52c91dfe4ccc5159417c660461bcce005e96" + assert_equal @data, @verifier.verify(signed_message) + end + + def test_rotating_secret + old_message = ActiveSupport::MessageVerifier.new("old", digest: "SHA1").generate("old") + + verifier = ActiveSupport::MessageVerifier.new(@secret, digest: "SHA1") + verifier.rotate "old" + + assert_equal "old", verifier.verified(old_message) + end + + def test_multiple_rotations + old_message = ActiveSupport::MessageVerifier.new("old", digest: "SHA256").generate("old") + older_message = ActiveSupport::MessageVerifier.new("older", digest: "SHA1").generate("older") + + verifier = ActiveSupport::MessageVerifier.new(@secret, digest: "SHA512") + verifier.rotate "old", digest: "SHA256" + verifier.rotate "older", digest: "SHA1" + + assert_equal "new", verifier.verified(verifier.generate("new")) + assert_equal "old", verifier.verified(old_message) + assert_equal "older", verifier.verified(older_message) + end + + def test_on_rotation_is_called_and_verified_returns_message + older_message = ActiveSupport::MessageVerifier.new("older", digest: "SHA1").generate(encoded: "message") + + verifier = ActiveSupport::MessageVerifier.new(@secret, digest: "SHA512") + verifier.rotate "old", digest: "SHA256" + verifier.rotate "older", digest: "SHA1" + + rotated = false + message = verifier.verified(older_message, on_rotation: proc { rotated = true }) + + assert_equal({ encoded: "message" }, message) + assert rotated + end + + def test_rotations_with_metadata + old_message = ActiveSupport::MessageVerifier.new("old").generate("old", purpose: :rotation) + + verifier = ActiveSupport::MessageVerifier.new(@secret) + verifier.rotate "old" + + assert_equal "old", verifier.verified(old_message, purpose: :rotation) + end +end + +class MessageVerifierMetadataTest < ActiveSupport::TestCase + include SharedMessageMetadataTests + + setup do + @verifier = ActiveSupport::MessageVerifier.new("Hey, I'm a secret!", verifier_options) + end + + def test_verify_raises_when_purpose_differs + assert_raise(ActiveSupport::MessageVerifier::InvalidSignature) do + @verifier.verify(generate(data, purpose: "payment"), purpose: "shipping") + end + end + + def test_verify_raises_when_expired + signed_message = generate(data, expires_in: 1.month) + + travel 2.months + assert_raise(ActiveSupport::MessageVerifier::InvalidSignature) do + @verifier.verify(signed_message) + end + end + + private + def generate(message, **options) + @verifier.generate(message, options) + end + + def parse(message, **options) + @verifier.verified(message, options) + end + + def verifier_options + Hash.new + end +end + +class MessageVerifierMetadataMarshalTest < MessageVerifierMetadataTest + private + def verifier_options + { serializer: Marshal } + end +end + +class MessageVerifierMetadataJSONTest < MessageVerifierMetadataTest + private + def verifier_options + { serializer: MessageVerifierTest::JSONSerializer.new } + end +end + +class MessageEncryptorMetadataNullSerializerTest < MessageVerifierMetadataTest + private + def data + "string message" + end + + def null_serializing? + true + end + + def verifier_options + { serializer: ActiveSupport::MessageEncryptor::NullSerializer } + end end diff --git a/activesupport/test/messages/rotation_configuration_test.rb b/activesupport/test/messages/rotation_configuration_test.rb new file mode 100644 index 0000000000..2f6824ed21 --- /dev/null +++ b/activesupport/test/messages/rotation_configuration_test.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require "abstract_unit" +require "active_support/messages/rotation_configuration" + +class MessagesRotationConfiguration < ActiveSupport::TestCase + def setup + @config = ActiveSupport::Messages::RotationConfiguration.new + end + + def test_signed_configurations + @config.rotate :signed, "older secret", salt: "salt", digest: "SHA1" + @config.rotate :signed, "old secret", salt: "salt", digest: "SHA256" + + assert_equal [ + [ "older secret", salt: "salt", digest: "SHA1" ], + [ "old secret", salt: "salt", digest: "SHA256" ] ], @config.signed + end + + def test_encrypted_configurations + @config.rotate :encrypted, "old raw key", cipher: "aes-256-gcm" + + assert_equal [ [ "old raw key", cipher: "aes-256-gcm" ] ], @config.encrypted + end +end diff --git a/activesupport/test/metadata/shared_metadata_tests.rb b/activesupport/test/metadata/shared_metadata_tests.rb new file mode 100644 index 0000000000..08bb0c648e --- /dev/null +++ b/activesupport/test/metadata/shared_metadata_tests.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +module SharedMessageMetadataTests + def teardown + travel_back + super + end + + def null_serializing? + false + end + + def test_encryption_and_decryption_with_same_purpose + assert_equal data, parse(generate(data, purpose: "checkout"), purpose: "checkout") + assert_equal data, parse(generate(data)) + + string_message = "address: #23, main street" + assert_equal string_message, parse(generate(string_message, purpose: "shipping"), purpose: "shipping") + end + + def test_verifies_array_when_purpose_matches + unless null_serializing? + data = [ "credit_card_no: 5012-6748-9087-5678", { "card_holder" => "Donald", "issued_on" => Time.local(2017) }, 12345 ] + assert_equal data, parse(generate(data, purpose: :registration), purpose: :registration) + end + end + + def test_encryption_and_decryption_with_different_purposes_returns_nil + assert_nil parse(generate(data, purpose: "payment"), purpose: "sign up") + assert_nil parse(generate(data, purpose: "payment")) + assert_nil parse(generate(data), purpose: "sign up") + end + + def test_purpose_using_symbols + assert_equal data, parse(generate(data, purpose: :checkout), purpose: :checkout) + assert_equal data, parse(generate(data, purpose: :checkout), purpose: "checkout") + assert_equal data, parse(generate(data, purpose: "checkout"), purpose: :checkout) + end + + def test_passing_expires_at_sets_expiration_date + encrypted_message = generate(data, expires_at: 1.hour.from_now) + + travel 59.minutes + assert_equal data, parse(encrypted_message) + + travel 2.minutes + assert_nil parse(encrypted_message) + end + + def test_set_relative_expiration_date_by_passing_expires_in + encrypted_message = generate(data, expires_in: 2.hours) + + travel 1.hour + assert_equal data, parse(encrypted_message) + + travel 1.hour + 1.second + assert_nil parse(encrypted_message) + end + + def test_passing_expires_in_less_than_a_second_is_not_expired + freeze_time do + encrypted_message = generate(data, expires_in: 1.second) + + travel 0.5.seconds + assert_equal data, parse(encrypted_message) + + travel 1.second + assert_nil parse(encrypted_message) + end + end + + def test_favor_expires_at_over_expires_in + payment_related_message = generate(data, purpose: "payment", expires_at: 2.year.from_now, expires_in: 1.second) + + travel 1.year + assert_equal data, parse(payment_related_message, purpose: :payment) + + travel 1.year + 1.day + assert_nil parse(payment_related_message, purpose: "payment") + end + + def test_skip_expires_at_and_expires_in_to_disable_expiration_check + payment_related_message = generate(data, purpose: "payment") + + travel 100.years + assert_equal data, parse(payment_related_message, purpose: "payment") + end + + private + def data + { "credit_card_no" => "5012-6784-9087-5678", "card_holder" => { "name" => "Donald" } } + end +end diff --git a/activesupport/test/multibyte_grapheme_break_conformance_test.rb b/activesupport/test/multibyte_grapheme_break_conformance_test.rb index 855626e779..fac74cd80f 100644 --- a/activesupport/test/multibyte_grapheme_break_conformance_test.rb +++ b/activesupport/test/multibyte_grapheme_break_conformance_test.rb @@ -1,4 +1,3 @@ -# encoding: utf-8 # frozen_string_literal: true require "abstract_unit" diff --git a/activesupport/test/multibyte_normalization_conformance_test.rb b/activesupport/test/multibyte_normalization_conformance_test.rb index deb94a7aa3..1173a94e81 100644 --- a/activesupport/test/multibyte_normalization_conformance_test.rb +++ b/activesupport/test/multibyte_normalization_conformance_test.rb @@ -1,4 +1,3 @@ -# encoding: utf-8 # frozen_string_literal: true require "abstract_unit" diff --git a/activesupport/test/ordered_options_test.rb b/activesupport/test/ordered_options_test.rb index 7f2e774c02..2c67bb02ac 100644 --- a/activesupport/test/ordered_options_test.rb +++ b/activesupport/test/ordered_options_test.rb @@ -102,4 +102,17 @@ class OrderedOptionsTest < ActiveSupport::TestCase end assert_raises(KeyError) { a.non_existing_key! } end + + def test_inheritable_options_with_bang + a = ActiveSupport::InheritableOptions.new(foo: :bar) + + assert_nothing_raised { a.foo! } + assert_equal a.foo, a.foo! + + assert_raises(KeyError) do + a.foo = nil + a.foo! + end + assert_raises(KeyError) { a.non_existing_key! } + end end diff --git a/activesupport/test/test_case_test.rb b/activesupport/test/test_case_test.rb index 8830c9b348..84e4953fe2 100644 --- a/activesupport/test/test_case_test.rb +++ b/activesupport/test/test_case_test.rb @@ -87,7 +87,8 @@ class AssertDifferenceTest < ActiveSupport::TestCase def test_expression_is_evaluated_in_the_appropriate_scope silence_warnings do - local_scope = local_scope = "foo" + local_scope = "foo" + local_scope = local_scope # to suppress unused variable warning assert_difference("local_scope; @object.num") { @object.increment } end end @@ -178,6 +179,7 @@ class AssertDifferenceTest < ActiveSupport::TestCase end def test_assert_changes_works_with_any_object + # Silences: instance variable @new_object not initialized. retval = silence_warnings do assert_changes :@new_object, from: nil, to: 42 do @new_object = 42 @@ -200,7 +202,7 @@ class AssertDifferenceTest < ActiveSupport::TestCase def test_assert_changes_with_to_and_case_operator token = nil - assert_changes "token", to: /\w{32}/ do + assert_changes -> { token }, to: /\w{32}/ do token = SecureRandom.hex end end @@ -208,7 +210,7 @@ class AssertDifferenceTest < ActiveSupport::TestCase def test_assert_changes_with_to_and_from_and_case_operator token = SecureRandom.hex - assert_changes "token", from: /\w{32}/, to: /\w{32}/ do + assert_changes -> { token }, from: /\w{32}/, to: /\w{32}/ do token = SecureRandom.hex end end @@ -235,7 +237,7 @@ class AssertDifferenceTest < ActiveSupport::TestCase end end - assert_equal "@object.num should not change.\n\"@object.num\" did change to 1.\nExpected: 0\n Actual: 1", error.message + assert_equal "@object.num should not change.\n\"@object.num\" did change to 1", error.message end end diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb index 04f1a53dd9..acb0ecd226 100644 --- a/activesupport/test/time_zone_test.rb +++ b/activesupport/test/time_zone_test.rb @@ -103,7 +103,6 @@ class TimeZoneTest < ActiveSupport::TestCase assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeZone["Eastern Time (US & Canada)"].today travel_to(Time.utc(2000, 1, 2, 5)) # midnight Jan 2 EST assert_equal Date.new(2000, 1, 2), ActiveSupport::TimeZone["Eastern Time (US & Canada)"].today - travel_back end def test_tomorrow @@ -115,7 +114,6 @@ class TimeZoneTest < ActiveSupport::TestCase assert_equal Date.new(2000, 1, 2), ActiveSupport::TimeZone["Eastern Time (US & Canada)"].tomorrow travel_to(Time.utc(2000, 1, 2, 5)) # midnight Jan 2 EST assert_equal Date.new(2000, 1, 3), ActiveSupport::TimeZone["Eastern Time (US & Canada)"].tomorrow - travel_back end def test_yesterday @@ -127,7 +125,6 @@ class TimeZoneTest < ActiveSupport::TestCase assert_equal Date.new(1999, 12, 31), ActiveSupport::TimeZone["Eastern Time (US & Canada)"].yesterday travel_to(Time.utc(2000, 1, 2, 5)) # midnight Jan 2 EST assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeZone["Eastern Time (US & Canada)"].yesterday - travel_back end def test_travel_to_a_date |