test: cover OrmDirtyEncryptedTest and teardown
This commit is contained in:
1 parent
f961ce99d2
commit
afe1cd4e81
1 file changed
+110
@@ -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
|
||||
Reference in new issue
Block a user