146 lines
5.6 KiB
Ruby
146 lines
5.6 KiB
Ruby
require 'minitest/autorun'
|
|
require 'json'
|
|
require 'prism'
|
|
require 'tmpdir'
|
|
|
|
class OrmSerializationTest < Minitest::Test
|
|
def setup
|
|
@dir = Dir.mktmpdir('prism-serialization-store')
|
|
@store = Prism::Orm::Store.new(File.join(@dir, 'test.pstore'))
|
|
@store.create_table(:authors, %w[id name email password_digest created_at])
|
|
@store.create_table(:articles, %w[id author_id title body])
|
|
|
|
author_class = Class.new(Prism::Orm::Model) do
|
|
table_name :authors
|
|
has_many :articles, class_name: 'Article', foreign_key: 'author_id'
|
|
has_secure_password validations: false
|
|
|
|
def greeting
|
|
"hi #{name}"
|
|
end
|
|
end
|
|
article_class = Class.new(Prism::Orm::Model) do
|
|
table_name :articles
|
|
belongs_to :author, class_name: 'Author', foreign_key: 'author_id'
|
|
end
|
|
[author_class, article_class].each { |klass| klass.store = @store }
|
|
Object.const_set(:Author, author_class)
|
|
Object.const_set(:Article, article_class)
|
|
end
|
|
|
|
def teardown
|
|
FileUtils.remove_entry(@dir)
|
|
%i[Author Article].each { |name| Object.send(:remove_const, name) if Object.const_defined?(name) }
|
|
end
|
|
|
|
def test_serializable_hash_returns_all_attributes_by_default
|
|
author = Author.create!(name: 'Ada', email: 'ada@example.com')
|
|
hash = author.serializable_hash
|
|
assert_equal 'Ada', hash['name']
|
|
assert_equal 'ada@example.com', hash['email']
|
|
assert_equal author.id, hash['id']
|
|
end
|
|
|
|
def test_only_filters_to_named_attributes
|
|
author = Author.create!(name: 'Ada', email: 'ada@example.com')
|
|
hash = author.serializable_hash(only: :name)
|
|
assert_equal({ 'name' => 'Ada' }, hash)
|
|
end
|
|
|
|
def test_except_removes_named_attributes
|
|
author = Author.create!(name: 'Ada', email: 'ada@example.com')
|
|
hash = author.serializable_hash(except: [:email, :created_at, :updated_at])
|
|
assert_equal %w[id name], hash.keys.sort
|
|
end
|
|
|
|
def test_methods_option_adds_computed_values
|
|
author = Author.create!(name: 'Ada', email: 'ada@example.com')
|
|
hash = author.serializable_hash(only: :name, methods: :greeting)
|
|
assert_equal 'hi Ada', hash['greeting']
|
|
end
|
|
|
|
def test_password_digest_is_hidden_from_json_by_default
|
|
author = Author.create!(name: 'Ada', email: 'ada@example.com', password: 'sekret123')
|
|
refute_nil author['password_digest']
|
|
hash = author.serializable_hash
|
|
refute hash.key?('password_digest'), 'password_digest must not leak through default serialization'
|
|
end
|
|
|
|
def test_explicit_only_can_override_the_default_hide
|
|
author = Author.create!(name: 'Ada', email: 'ada@example.com', password: 'sekret123')
|
|
hash = author.serializable_hash(only: %i[name password_digest])
|
|
assert hash.key?('password_digest'), 'an explicit only: is a deliberate opt-in, it must win'
|
|
end
|
|
|
|
def test_as_json_stringifies_time_columns
|
|
author = Author.create!(name: 'Ada', email: 'ada@example.com')
|
|
assert_kind_of Time, author['created_at'], 'sanity check: raw attribute is a Time'
|
|
json = author.as_json
|
|
assert_kind_of String, json['created_at']
|
|
assert_equal author['created_at'].iso8601, json['created_at']
|
|
end
|
|
|
|
def test_to_json_round_trips_through_json_generate
|
|
author = Author.create!(name: 'Ada', email: 'ada@example.com')
|
|
parsed = JSON.parse(author.to_json)
|
|
assert_equal 'Ada', parsed['name']
|
|
refute parsed.key?('password_digest')
|
|
end
|
|
|
|
def test_json_generate_on_a_bare_record_uses_to_json
|
|
author = Author.create!(name: 'Ada', email: 'ada@example.com')
|
|
parsed = JSON.parse(JSON.generate(author))
|
|
assert_equal 'Ada', parsed['name']
|
|
end
|
|
|
|
def test_include_serializes_a_belongs_to_association
|
|
author = Author.create!(name: 'Ada', email: 'ada@example.com')
|
|
article = Article.create!(author_id: author.id, title: 'Hello', body: 'World')
|
|
hash = article.serializable_hash(include: :author)
|
|
assert_equal 'Ada', hash['author']['name']
|
|
end
|
|
|
|
def test_include_serializes_a_has_many_collection
|
|
author = Author.create!(name: 'Ada', email: 'ada@example.com')
|
|
Article.create!(author_id: author.id, title: 'One', body: 'x')
|
|
Article.create!(author_id: author.id, title: 'Two', body: 'y')
|
|
hash = author.serializable_hash(only: :name, include: { articles: { only: :title } })
|
|
titles = hash['articles'].map { |row| row['title'] }
|
|
assert_equal %w[One Two], titles.sort
|
|
end
|
|
|
|
def test_include_with_nil_belongs_to_yields_nil
|
|
orphan = Article.create!(author_id: nil, title: 'Orphan', body: 'x')
|
|
hash = orphan.serializable_hash(include: :author)
|
|
assert_nil hash['author']
|
|
end
|
|
|
|
def test_relation_as_json_serializes_each_record
|
|
Author.create!(name: 'Ada', email: 'a@example.com')
|
|
Author.create!(name: 'Bea', email: 'b@example.com')
|
|
rows = Author.all.as_json(only: :name)
|
|
assert_equal [{ 'name' => 'Ada' }, { 'name' => 'Bea' }], rows.sort_by { |row| row['name'] }
|
|
end
|
|
|
|
def test_relation_to_json_produces_a_json_array_without_leaking_hidden_columns
|
|
Author.create!(name: 'Ada', email: 'a@example.com', password: 'sekret123')
|
|
parsed = JSON.parse(Author.all.to_json)
|
|
assert_equal 1, parsed.size
|
|
refute parsed.first.key?('password_digest')
|
|
end
|
|
|
|
def test_hide_from_json_is_inherited_by_subclasses
|
|
base_class = Class.new(Prism::Orm::Model) { table_name :authors }
|
|
base_class.hide_from_json(:email)
|
|
Object.const_set(:BaseAuthor, base_class)
|
|
sub_class = Class.new(base_class) { table_name :authors }
|
|
Object.const_set(:SubAuthor, sub_class)
|
|
sub_class.store = @store
|
|
|
|
record = sub_class.create!(name: 'Ada', email: 'ada@example.com')
|
|
refute record.serializable_hash.key?('email')
|
|
ensure
|
|
%i[BaseAuthor SubAuthor].each { |name| Object.send(:remove_const, name) if Object.const_defined?(name) }
|
|
end
|
|
end
|