test: cover OrmDirtyEncryptedTest and teardown

This commit is contained in:
dualfroz committed 2026-09-11 20:55:33 +02:00
1 parent f961ce99d2
commit afe1cd4e81
1 file changed
+110
+110
View File
@@ -0,0 +1,110 @@
require 'minitest/autorun'
require 'prism'
require 'tmpdir'
# Session 55/56/58 left this as a known, unverified gap: Dirty (ActiveModel::
# Dirty-lite) reads @attributes directly, so on an `encrypts`-backed column
# every dirty-tracking call (changed_attributes/changes/attribute_was/
# attribute_change) leaked raw ciphertext instead of the plaintext the
# model's own reader returns. This file is the end-to-end proof that the
# fix (Dirty#humanize_dirty_value, Encryption's setter no-op-on-same-value)
# actually closes that gap, on the same non-deterministic and deterministic
# split test_orm_encryption.rb already exercises for plain reads.
class OrmDirtyEncryptedTest < Minitest::Test
def setup
@dir = Dir.mktmpdir('prism-dirty-encrypted-store')
@store = Prism::Orm::Store.new(File.join(@dir, 'test.pstore'))
@store.create_table(:patients, %w[id name ssn notes])
Prism::Orm::Encryption.primary_key = 'test-primary-key-do-not-use-in-prod'
patient_class = Class.new(Prism::Orm::Model) do
table_name :patients
encrypts :ssn, deterministic: true
encrypts :notes
end
patient_class.store = @store
Object.const_set(:DirtyEncryptedPatient, patient_class)
end
def teardown
FileUtils.remove_entry(@dir)
Prism::Orm::Encryption.primary_key = nil
Object.send(:remove_const, :DirtyEncryptedPatient) if Object.const_defined?(:DirtyEncryptedPatient)
end
def test_new_record_reports_plaintext_not_ciphertext_in_changes
patient = DirtyEncryptedPatient.new(name: 'Alice', ssn: '123-45-6789', notes: 'note')
old_value, new_value = patient.changes['ssn']
assert_nil old_value
assert_equal '123-45-6789', new_value
refute_includes patient.changes['ssn'].to_s, patient['ssn'].to_s
end
def test_changed_attributes_reports_plaintext_old_value
patient = DirtyEncryptedPatient.create!(name: 'Alice', ssn: '123-45-6789', notes: 'old note')
patient.notes = 'new note'
assert_equal 'old note', patient.changed_attributes['notes']
end
def test_attribute_was_and_attribute_change_report_plaintext_on_deterministic_column
patient = DirtyEncryptedPatient.create!(name: 'Alice', ssn: '123-45-6789', notes: 'n')
patient.ssn = '999-99-9999'
assert_equal '123-45-6789', patient.attribute_was('ssn')
assert_equal ['123-45-6789', '999-99-9999'], patient.attribute_change('ssn')
end
def test_attribute_was_and_attribute_change_report_plaintext_on_nondeterministic_column
patient = DirtyEncryptedPatient.create!(name: 'Alice', ssn: '123-45-6789', notes: 'old note')
patient.notes = 'new note'
assert_equal 'old note', patient.attribute_was('notes')
assert_equal ['old note', 'new note'], patient.attribute_change('notes')
end
def test_reassigning_the_same_plaintext_on_a_nondeterministic_column_is_not_dirty
patient = DirtyEncryptedPatient.create!(name: 'Alice', ssn: '123-45-6789', notes: 'same note')
raw_before = patient['notes']
patient.notes = 'same note'
refute patient.attribute_changed?('notes'), 'assigning the value already held must not be dirty'
refute patient.changed?
assert_equal raw_before, patient['notes'], 'a true no-op must not even rewrite the ciphertext'
end
def test_reassigning_the_same_plaintext_on_a_deterministic_column_is_not_dirty
patient = DirtyEncryptedPatient.create!(name: 'Alice', ssn: '123-45-6789', notes: 'n')
patient.ssn = '123-45-6789'
refute patient.attribute_changed?('ssn')
refute patient.changed?
end
def test_saved_changes_after_create_reports_plaintext
patient = DirtyEncryptedPatient.create!(name: 'Alice', ssn: '123-45-6789', notes: 'note')
assert_equal [nil, '123-45-6789'], patient.saved_changes['ssn']
assert_equal [nil, 'note'], patient.saved_changes['notes']
end
def test_saved_changes_after_update_reports_plaintext_and_only_the_real_change
patient = DirtyEncryptedPatient.create!(name: 'Alice', ssn: '123-45-6789', notes: 'old note')
patient.update!(notes: 'new note')
assert_equal ['old note', 'new note'], patient.saved_changes['notes']
refute_includes patient.saved_changes.keys, 'ssn'
end
def test_restore_attributes_still_reverts_the_underlying_ciphertext_correctly
patient = DirtyEncryptedPatient.create!(name: 'Alice', ssn: '123-45-6789', notes: 'old note')
patient.notes = 'new note'
patient.restore_attributes(['notes'])
assert_equal 'old note', patient.notes
refute patient.changed?
end
def test_a_corrupted_current_value_does_not_block_overwriting_it
patient = DirtyEncryptedPatient.create!(name: 'Alice', ssn: '123-45-6789', notes: 'note')
row = @store.find(:patients, patient.id)
tampered = row.dup
tampered['notes'] = row['notes'].sub(/^./) { |c| c == 'A' ? 'B' : 'A' }
@store.update(:patients, patient.id, tampered)
reloaded = DirtyEncryptedPatient.find(patient.id)
reloaded.notes = 'fresh note'
assert_equal 'fresh note', reloaded.notes
end
end