From 7388c82502bac6b3dc9e88d6e60beed6ff8bf383 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Sun, 13 Sep 2026 18:18:31 +0200 Subject: [PATCH] test: cover OrmTypeCastingTest, test_nil_stays_nil_for_every_type --- test/test_orm_type_casting.rb | 107 ++++++++++++++++++++++++++++++ test/test_orm_uniqueness_scope.rb | 73 ++++++++++++++++++++ 2 files changed, 180 insertions(+) create mode 100644 test/test_orm_type_casting.rb create mode 100644 test/test_orm_uniqueness_scope.rb diff --git a/test/test_orm_type_casting.rb b/test/test_orm_type_casting.rb new file mode 100644 index 0000000..d9624c5 --- /dev/null +++ b/test/test_orm_type_casting.rb @@ -0,0 +1,107 @@ +require 'minitest/autorun' +require 'prism' + +class OrmTypeCastingTest < Minitest::Test + Cast = Prism::Orm::TypeCasting + + def test_nil_stays_nil_for_every_type + %i[string text integer float boolean datetime].each do |type| + assert_nil Cast.cast(type, nil), "expected nil to survive :#{type}" + end + end + + def test_unknown_type_raises_argument_error + assert_raises(ArgumentError) { Cast.cast(:money, '5') } + end + + def test_string_and_text_stringify_non_strings + assert_equal '5', Cast.cast(:string, 5) + assert_equal '5', Cast.cast(:text, 5) + assert_equal 'already', Cast.cast(:string, 'already') + end + + def test_integer_parses_numeric_strings + assert_equal 42, Cast.cast(:integer, '42') + assert_equal(-3, Cast.cast(:integer, '-3')) + assert_equal 7, Cast.cast(:integer, ' 7 ') + end + + def test_integer_passes_through_actual_integers_and_numerics + assert_equal 42, Cast.cast(:integer, 42) + assert_equal 3, Cast.cast(:integer, 3.9) + end + + def test_integer_blank_string_is_nil_not_zero + assert_nil Cast.cast(:integer, '') + assert_nil Cast.cast(:integer, ' ') + end + + def test_integer_garbage_raises_invalid_cast + error = assert_raises(Prism::Orm::TypeCasting::InvalidCast) { Cast.cast(:integer, 'abc') } + assert_match(/abc/, error.message) + end + + def test_integer_rejects_float_looking_strings + assert_raises(Prism::Orm::TypeCasting::InvalidCast) { Cast.cast(:integer, '4.5') } + end + + def test_float_parses_numeric_strings + assert_in_delta 4.5, Cast.cast(:float, '4.5') + assert_in_delta 4.0, Cast.cast(:float, '4') + end + + def test_float_blank_string_is_nil + assert_nil Cast.cast(:float, '') + end + + def test_float_garbage_raises_invalid_cast + assert_raises(Prism::Orm::TypeCasting::InvalidCast) { Cast.cast(:float, 'nope') } + end + + def test_boolean_recognizes_common_true_strings + %w[1 t true y yes on TRUE Yes On].each do |value| + assert_equal true, Cast.cast(:boolean, value), "expected #{value.inspect} to cast true" + end + end + + def test_boolean_recognizes_common_false_strings + %w[0 f false n no off FALSE No Off].each do |value| + assert_equal false, Cast.cast(:boolean, value), "expected #{value.inspect} to cast false" + end + end + + def test_boolean_passes_through_actual_booleans + assert_equal true, Cast.cast(:boolean, true) + assert_equal false, Cast.cast(:boolean, false) + end + + def test_boolean_garbage_raises_invalid_cast_instead_of_guessing_truthy + error = assert_raises(Prism::Orm::TypeCasting::InvalidCast) { Cast.cast(:boolean, 'banana') } + assert_match(/banana/, error.message) + end + + def test_datetime_passes_through_time + now = Time.now + assert_same now, Cast.cast(:datetime, now) + end + + def test_datetime_parses_iso8601_strings + result = Cast.cast(:datetime, '2026-01-15T10:30:00Z') + assert_kind_of Time, result + assert_equal 2026, result.year + assert_equal 1, result.month + assert_equal 15, result.day + end + + def test_datetime_converts_date + result = Cast.cast(:datetime, Date.new(2026, 3, 4)) + assert_kind_of Time, result + assert_equal 2026, result.year + assert_equal 3, result.month + assert_equal 4, result.day + end + + def test_datetime_garbage_raises_invalid_cast + assert_raises(Prism::Orm::TypeCasting::InvalidCast) { Cast.cast(:datetime, 'not a date') } + end +end diff --git a/test/test_orm_uniqueness_scope.rb b/test/test_orm_uniqueness_scope.rb new file mode 100644 index 0000000..d2b6e45 --- /dev/null +++ b/test/test_orm_uniqueness_scope.rb @@ -0,0 +1,73 @@ +require 'minitest/autorun' +require 'prism' +require 'tmpdir' + +class OrmUniquenessScopeTest < Minitest::Test + def setup + @dir = Dir.mktmpdir('prism-uniqueness-scope-store') + @store = Prism::Orm::Store.new(File.join(@dir, 'test.pstore')) + @store.create_table(:likes, %w[id likeable_id likeable_type user_id]) + + like_class = Class.new(Prism::Orm::Model) do + table_name :likes + validates :user_id, presence: true, uniqueness: { scope: %i[likeable_id likeable_type] } + end + like_class.store = @store + Object.const_set(:Like, like_class) + end + + def teardown + FileUtils.remove_entry(@dir) + Object.send(:remove_const, :Like) if Object.const_defined?(:Like) + end + + def test_rejects_a_second_like_from_the_same_user_on_the_same_target + Like.create!(user_id: 1, likeable_id: 5, likeable_type: 'Post') + duplicate = Like.new(user_id: 1, likeable_id: 5, likeable_type: 'Post') + + refute duplicate.valid? + assert_includes duplicate.errors[:user_id], 'must be unique' + end + + def test_allows_the_same_user_to_like_a_different_target + Like.create!(user_id: 1, likeable_id: 5, likeable_type: 'Post') + other_target = Like.new(user_id: 1, likeable_id: 6, likeable_type: 'Post') + + assert other_target.valid? + end + + def test_allows_the_same_user_to_like_the_same_id_on_a_different_type + Like.create!(user_id: 1, likeable_id: 5, likeable_type: 'Post') + other_type = Like.new(user_id: 1, likeable_id: 5, likeable_type: 'Photo') + + assert other_type.valid? + end + + def test_allows_a_different_user_to_like_the_same_target + Like.create!(user_id: 1, likeable_id: 5, likeable_type: 'Post') + other_user = Like.new(user_id: 2, likeable_id: 5, likeable_type: 'Post') + + assert other_user.valid? + end + + def test_saving_the_same_record_twice_is_not_a_conflict_with_itself + like = Like.create!(user_id: 1, likeable_id: 5, likeable_type: 'Post') + like.user_id = 1 + + assert like.valid? + end + + def test_unscoped_uniqueness_still_works_without_a_scope_option + scoreless_store = Prism::Orm::Store.new(File.join(@dir, 'scoreless.pstore')) + scoreless_store.create_table(:tags, %w[id name]) + tag_class = Class.new(Prism::Orm::Model) do + table_name :tags + validates :name, presence: true, uniqueness: true + end + tag_class.store = scoreless_store + tag_class.create!(name: 'ruby') + + duplicate = tag_class.new(name: 'ruby') + refute duplicate.valid? + end +end