From 7cf239f4e39a8cd7bc9522e712eac54f323c1268 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Fri, 28 Aug 2026 09:00:00 +0200 Subject: [PATCH] test: add orm association and validation tests --- test/test_orm_associations.rb | 94 +++++++++++++++++++++++++++++++++++ test/test_orm_validations.rb | 69 +++++++++++++++++++++++++ 2 files changed, 163 insertions(+) create mode 100644 test/test_orm_associations.rb create mode 100644 test/test_orm_validations.rb diff --git a/test/test_orm_associations.rb b/test/test_orm_associations.rb new file mode 100644 index 0000000..508f0cf --- /dev/null +++ b/test/test_orm_associations.rb @@ -0,0 +1,94 @@ +require 'minitest/autorun' +require 'prism' +require 'tmpdir' + +class OrmAssociationsTest < Minitest::Test + def setup + @dir = Dir.mktmpdir('prism-assoc-store') + @store = Prism::Orm::Store.new(File.join(@dir, 'test.pstore')) + @store.create_table(:authors, %w[id name]) + @store.create_table(:posts, %w[id title author_id]) + + author_class = Class.new(Prism::Orm::Model) do + table_name :authors + has_many :posts, class_name: 'Post', foreign_key: 'author_id' + end + post_class = Class.new(Prism::Orm::Model) do + table_name :posts + belongs_to :author, class_name: 'Author', foreign_key: 'author_id' + end + author_class.store = @store + post_class.store = @store + Object.const_set(:Author, author_class) + Object.const_set(:Post, post_class) + end + + def teardown + FileUtils.remove_entry(@dir) + %i[Author Post].each { |name| Object.send(:remove_const, name) if Object.const_defined?(name) } + end + + def test_belongs_to_returns_associated_record + author = Author.create!(name: 'foksiu') + post = Post.create!(title: 'hello', author_id: author.id) + assert_equal author, post.author + end + + def test_belongs_to_setter_updates_foreign_key + author = Author.create!(name: 'foksiu') + post = Post.create!(title: 'hello') + post.author = author + assert_equal author.id, post.author_id + end + + def test_belongs_to_returns_nil_when_unset + post = Post.create!(title: 'orphan') + assert_nil post.author + end + + def test_has_many_returns_associated_records + author = Author.create!(name: 'foksiu') + Post.create!(title: 'first', author_id: author.id) + Post.create!(title: 'second', author_id: author.id) + Post.create!(title: 'not mine', author_id: nil) + + titles = author.posts.map(&:title).sort + assert_equal %w[first second], titles + end + + def test_where_with_array_condition + a = Author.create!(name: 'a') + b = Author.create!(name: 'b') + Author.create!(name: 'c') + matched = Author.where(id: [a.id, b.id]).map(&:name).sort + assert_equal %w[a b], matched + end + + def test_order_and_limit + Author.create!(name: 'charlie') + Author.create!(name: 'alice') + Author.create!(name: 'bob') + names = Author.order(:name).limit(2).map(&:name) + assert_equal %w[alice bob], names + end + + def test_order_desc + Author.create!(name: 'alice') + Author.create!(name: 'bob') + names = Author.order(:name, :desc).map(&:name) + assert_equal %w[bob alice], names + end + + def test_find_by + Author.create!(name: 'findme') + found = Author.find_by(name: 'findme') + refute_nil found + assert_nil Author.find_by(name: 'nope') + end + + def test_pluck + Author.create!(name: 'a') + Author.create!(name: 'b') + assert_equal %w[a b], Author.all.pluck(:name) + end +end diff --git a/test/test_orm_validations.rb b/test/test_orm_validations.rb new file mode 100644 index 0000000..d1bc90e --- /dev/null +++ b/test/test_orm_validations.rb @@ -0,0 +1,69 @@ +require 'minitest/autorun' +require 'prism' +require 'tmpdir' + +class OrmValidationsTest < Minitest::Test + def setup + @dir = Dir.mktmpdir('prism-validation-store') + @store = Prism::Orm::Store.new(File.join(@dir, 'test.pstore')) + @store.create_table(:accounts, %w[id email nickname]) + account_class = Class.new(Prism::Orm::Model) do + table_name :accounts + validates :email, presence: true, uniqueness: true, format: { with: /\A[^@\s]+@[^@\s]+\z/ } + validates :nickname, length: { minimum: 2, maximum: 10 } + end + account_class.store = @store + Object.const_set(:Account, account_class) + end + + def teardown + FileUtils.remove_entry(@dir) + Object.send(:remove_const, :Account) if Object.const_defined?(:Account) + end + + def test_presence_validation_blocks_save + account = Account.new(email: '') + refute account.valid? + assert_includes account.errors[:email], 'must be present' + end + + def test_format_validation_rejects_bad_email + account = Account.new(email: 'not-an-email', nickname: 'foksiu') + refute account.valid? + assert_includes account.errors[:email], 'is invalid' + end + + def test_valid_record_has_no_errors + account = Account.new(email: 'foksiu@example.com', nickname: 'foksiu') + assert account.valid? + assert_empty account.errors + end + + def test_uniqueness_rejects_duplicate_email + Account.create!(email: 'taken@example.com', nickname: 'first') + dupe = Account.new(email: 'taken@example.com', nickname: 'second') + refute dupe.valid? + assert_includes dupe.errors[:email], 'must be unique' + end + + def test_uniqueness_allows_saving_same_record_twice + account = Account.create!(email: 'stable@example.com', nickname: 'stable') + account.nickname = 'stable2' + assert account.save, account.errors.full_messages.join(', ') + end + + def test_length_validation + account = Account.new(email: 'a@example.com', nickname: 'x') + refute account.valid? + assert_includes account.errors[:nickname], 'is too short (minimum 2)' + end + + def test_custom_message + Account.validates(:email, format: { with: /@/, message: 'needs an @ sign' }) + account = Account.new(email: 'nope', nickname: 'foksiu') + refute account.valid? + assert_includes account.errors[:email], 'needs an @ sign' + ensure + Account.validators.pop + end +end