powerhome-attr_encrypted 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +6 -0
  3. data/.travis.yml +67 -0
  4. data/CHANGELOG.md +98 -0
  5. data/Gemfile +3 -0
  6. data/MIT-LICENSE +20 -0
  7. data/README.md +465 -0
  8. data/Rakefile +25 -0
  9. data/attr_encrypted.gemspec +63 -0
  10. data/certs/saghaulor.pem +21 -0
  11. data/checksum/attr_encrypted-3.0.0.gem.sha256 +1 -0
  12. data/checksum/attr_encrypted-3.0.0.gem.sha512 +1 -0
  13. data/checksum/attr_encrypted-3.0.1.gem.sha256 +1 -0
  14. data/checksum/attr_encrypted-3.0.1.gem.sha512 +1 -0
  15. data/checksum/attr_encrypted-3.0.2.gem.sha256 +1 -0
  16. data/checksum/attr_encrypted-3.0.2.gem.sha512 +1 -0
  17. data/checksum/attr_encrypted-3.0.3.gem.sha256 +1 -0
  18. data/checksum/attr_encrypted-3.0.3.gem.sha512 +1 -0
  19. data/checksum/attr_encrypted-3.1.0.gem.sha256 +1 -0
  20. data/checksum/attr_encrypted-3.1.0.gem.sha512 +1 -0
  21. data/lib/attr_encrypted.rb +473 -0
  22. data/lib/attr_encrypted/adapters/active_record.rb +157 -0
  23. data/lib/attr_encrypted/adapters/data_mapper.rb +24 -0
  24. data/lib/attr_encrypted/adapters/sequel.rb +16 -0
  25. data/lib/attr_encrypted/version.rb +19 -0
  26. data/test/active_record_test.rb +365 -0
  27. data/test/attr_encrypted_test.rb +490 -0
  28. data/test/compatibility_test.rb +109 -0
  29. data/test/data_mapper_test.rb +59 -0
  30. data/test/legacy_active_record_test.rb +120 -0
  31. data/test/legacy_attr_encrypted_test.rb +300 -0
  32. data/test/legacy_compatibility_test.rb +95 -0
  33. data/test/legacy_data_mapper_test.rb +57 -0
  34. data/test/legacy_sequel_test.rb +54 -0
  35. data/test/run.sh +12 -0
  36. data/test/sequel_test.rb +55 -0
  37. data/test/test_helper.rb +61 -0
  38. metadata +294 -0
@@ -0,0 +1,490 @@
1
+ # frozen_string_literal: true
2
+
3
+ # encoding: UTF-8
4
+ require_relative 'test_helper'
5
+
6
+ class SillyEncryptor
7
+ def self.silly_encrypt(options)
8
+ (options[:value] + options[:some_arg]).reverse
9
+ end
10
+
11
+ def self.silly_decrypt(options)
12
+ options[:value].reverse.gsub(/#{options[:some_arg]}$/, '')
13
+ end
14
+ end
15
+
16
+ class User
17
+ extend AttrEncrypted
18
+ self.attr_encrypted_options[:key] = Proc.new { |user| SECRET_KEY } # default key
19
+ self.attr_encrypted_options[:mode] = :per_attribute_iv_and_salt
20
+
21
+ attr_encrypted :email, :without_encoding, :key => SECRET_KEY
22
+ attr_encrypted :password, :prefix => 'crypted_', :suffix => '_test'
23
+ attr_encrypted :ssn, :key => :secret_key, :attribute => 'ssn_encrypted'
24
+ attr_encrypted :credit_card, :encryptor => SillyEncryptor, :encrypt_method => :silly_encrypt, :decrypt_method => :silly_decrypt, :some_arg => 'test'
25
+ attr_encrypted :with_encoding, :key => SECRET_KEY, :encode => true
26
+ attr_encrypted :with_custom_encoding, :key => SECRET_KEY, :encode => 'm'
27
+ attr_encrypted :with_marshaling, :key => SECRET_KEY, :marshal => true
28
+ attr_encrypted :with_true_if, :key => SECRET_KEY, :if => true, mode: :per_attribute_iv_and_salt
29
+ attr_encrypted :with_false_if, :key => SECRET_KEY, :if => false, mode: :per_attribute_iv_and_salt
30
+ attr_encrypted :with_true_unless, :key => SECRET_KEY, :unless => true, mode: :per_attribute_iv_and_salt
31
+ attr_encrypted :with_false_unless, :key => SECRET_KEY, :unless => false, mode: :per_attribute_iv_and_salt
32
+ attr_encrypted :with_if_changed, :key => SECRET_KEY, :if => :should_encrypt
33
+ attr_encrypted :with_allow_empty_value, key: SECRET_KEY, allow_empty_value: true, marshal: true
34
+
35
+ attr_encryptor :aliased, :key => SECRET_KEY
36
+
37
+ attr_accessor :salt
38
+ attr_accessor :should_encrypt
39
+
40
+ def initialize(email: nil)
41
+ self.email = email
42
+ self.salt = Time.now.to_i.to_s
43
+ self.should_encrypt = true
44
+ end
45
+
46
+ private
47
+ def secret_key
48
+ SECRET_KEY
49
+ end
50
+ end
51
+
52
+ class Admin < User
53
+ attr_encrypted :testing
54
+ end
55
+
56
+ class SomeOtherClass
57
+ extend AttrEncrypted
58
+ def self.call(object)
59
+ object.class
60
+ end
61
+ end
62
+
63
+ class YetAnotherClass
64
+ extend AttrEncrypted
65
+ self.attr_encrypted_options[:encode_iv] = false
66
+
67
+ attr_encrypted :email, :key => SECRET_KEY
68
+ attr_encrypted :phone_number, :key => SECRET_KEY, mode: Proc.new { |thing| thing.mode }, encode_iv: Proc.new { |thing| thing.encode_iv }, encode_salt: Proc.new { |thing| thing.encode_salt }
69
+
70
+ def initialize(email: nil, encode_iv: 'm', encode_salt: 'm', mode: :per_attribute_iv_and_salt)
71
+ self.email = email
72
+ @encode_iv = encode_iv
73
+ @encode_salt = encode_salt
74
+ @mode = mode
75
+ end
76
+
77
+ attr_reader :encode_iv, :encode_salt, :mode
78
+ end
79
+
80
+ class AttrEncryptedTest < Minitest::Test
81
+ def setup
82
+ @iv = SecureRandom.random_bytes(12)
83
+ end
84
+
85
+ def test_should_store_email_in_encrypted_attributes
86
+ assert User.encrypted_attributes.include?(:email)
87
+ end
88
+
89
+ def test_should_not_store_salt_in_encrypted_attributes
90
+ refute User.encrypted_attributes.include?(:salt)
91
+ end
92
+
93
+ def test_attr_encrypted_should_return_true_for_email
94
+ assert User.attr_encrypted?('email')
95
+ end
96
+
97
+ def test_attr_encrypted_should_not_use_the_same_attribute_name_for_two_attributes_in_the_same_line
98
+ refute_equal User.encrypted_attributes[:email][:attribute], User.encrypted_attributes[:without_encoding][:attribute]
99
+ end
100
+
101
+ def test_attr_encrypted_should_return_false_for_salt
102
+ assert !User.attr_encrypted?('salt')
103
+ end
104
+
105
+ def test_should_generate_an_encrypted_attribute
106
+ assert User.new.respond_to?(:encrypted_email)
107
+ end
108
+
109
+ def test_should_generate_an_encrypted_attribute_with_a_prefix_and_suffix
110
+ assert User.new.respond_to?(:crypted_password_test)
111
+ end
112
+
113
+ def test_should_generate_an_encrypted_attribute_with_the_attribute_option
114
+ assert User.new.respond_to?(:ssn_encrypted)
115
+ end
116
+
117
+ def test_should_not_encrypt_nil_value
118
+ assert_nil User.encrypt_email(nil, iv: @iv)
119
+ end
120
+
121
+ def test_should_not_encrypt_empty_string_by_default
122
+ assert_equal '', User.encrypt_email('', iv: @iv)
123
+ end
124
+
125
+ def test_should_encrypt_email
126
+ refute_nil User.encrypt_email('test@example.com', iv: @iv)
127
+ refute_equal 'test@example.com', User.encrypt_email('test@example.com', iv: @iv)
128
+ end
129
+
130
+ def test_should_encrypt_email_when_modifying_the_attr_writer
131
+ @user = User.new
132
+ assert_nil @user.encrypted_email
133
+ @user.email = 'test@example.com'
134
+ refute_nil @user.encrypted_email
135
+ iv = @user.encrypted_email_iv.unpack('m').first
136
+ salt = @user.encrypted_email_salt[1..-1].unpack('m').first
137
+ assert_equal User.encrypt_email('test@example.com', iv: iv, salt: salt), @user.encrypted_email
138
+ end
139
+
140
+ def test_should_not_decrypt_nil_value
141
+ assert_nil User.decrypt_email(nil, iv: @iv)
142
+ end
143
+
144
+ def test_should_not_decrypt_empty_string
145
+ assert_equal '', User.decrypt_email('', iv: @iv)
146
+ end
147
+
148
+ def test_should_decrypt_email
149
+ encrypted_email = User.encrypt_email('test@example.com', iv: @iv)
150
+ refute_equal 'test@test.com', encrypted_email
151
+ assert_equal 'test@example.com', User.decrypt_email(encrypted_email, iv: @iv)
152
+ end
153
+
154
+ def test_should_decrypt_email_when_reading
155
+ @user = User.new
156
+ assert_nil @user.email
157
+ options = @user.encrypted_attributes[:email]
158
+ iv = @user.send(:generate_iv, options[:algorithm])
159
+ encoded_iv = [iv].pack(options[:encode_iv])
160
+ salt = SecureRandom.random_bytes
161
+ encoded_salt = @user.send(:prefix_and_encode_salt, salt, options[:encode_salt])
162
+ @user.encrypted_email = User.encrypt_email('test@example.com', iv: iv, salt: salt)
163
+ @user.encrypted_email_iv = encoded_iv
164
+ @user.encrypted_email_salt = encoded_salt
165
+ assert_equal 'test@example.com', @user.email
166
+ end
167
+
168
+ def test_should_encrypt_with_encoding
169
+ assert_equal User.encrypt_with_encoding('test', iv: @iv), [User.encrypt_without_encoding('test', iv: @iv)].pack('m')
170
+ end
171
+
172
+ def test_should_decrypt_with_encoding
173
+ encrypted = User.encrypt_with_encoding('test', iv: @iv)
174
+ assert_equal 'test', User.decrypt_with_encoding(encrypted, iv: @iv)
175
+ assert_equal User.decrypt_with_encoding(encrypted, iv: @iv), User.decrypt_without_encoding(encrypted.unpack('m').first, iv: @iv)
176
+ end
177
+
178
+ def test_should_encrypt_with_custom_encoding
179
+ assert_equal User.encrypt_with_encoding('test', iv: @iv), [User.encrypt_without_encoding('test', iv: @iv)].pack('m')
180
+ end
181
+
182
+ def test_should_decrypt_with_custom_encoding
183
+ encrypted = User.encrypt_with_encoding('test', iv: @iv)
184
+ assert_equal 'test', User.decrypt_with_encoding(encrypted, iv: @iv)
185
+ assert_equal User.decrypt_with_encoding(encrypted, iv: @iv), User.decrypt_without_encoding(encrypted.unpack('m').first, iv: @iv)
186
+ end
187
+
188
+ def test_should_encrypt_with_marshaling
189
+ @user = User.new
190
+ @user.with_marshaling = [1, 2, 3]
191
+ refute_nil @user.encrypted_with_marshaling
192
+ end
193
+
194
+ def test_should_use_custom_encryptor_and_crypt_method_names_and_arguments
195
+ assert_equal SillyEncryptor.silly_encrypt(:value => 'testing', :some_arg => 'test'), User.encrypt_credit_card('testing')
196
+ end
197
+
198
+ def test_should_evaluate_a_key_passed_as_a_symbol
199
+ @user = User.new
200
+ assert_nil @user.ssn_encrypted
201
+ @user.ssn = 'testing'
202
+ refute_nil @user.ssn_encrypted
203
+ encrypted = Encryptor.encrypt(:value => 'testing', :key => SECRET_KEY, :iv => @user.ssn_encrypted_iv.unpack("m").first, :salt => @user.ssn_encrypted_salt.unpack("m").first )
204
+ assert_equal encrypted, @user.ssn_encrypted
205
+ end
206
+
207
+ def test_should_evaluate_a_key_passed_as_a_proc
208
+ @user = User.new
209
+ assert_nil @user.crypted_password_test
210
+ @user.password = 'testing'
211
+ refute_nil @user.crypted_password_test
212
+ encrypted = Encryptor.encrypt(:value => 'testing', :key => SECRET_KEY, :iv => @user.crypted_password_test_iv.unpack("m").first, :salt => @user.crypted_password_test_salt.unpack("m").first)
213
+ assert_equal encrypted, @user.crypted_password_test
214
+ end
215
+
216
+ def test_should_use_options_found_in_the_attr_encrypted_options_attribute
217
+ @user = User.new
218
+ assert_nil @user.crypted_password_test
219
+ @user.password = 'testing'
220
+ refute_nil @user.crypted_password_test
221
+ encrypted = Encryptor.encrypt(:value => 'testing', :key => SECRET_KEY, :iv => @user.crypted_password_test_iv.unpack("m").first, :salt => @user.crypted_password_test_salt.unpack("m").first)
222
+ assert_equal encrypted, @user.crypted_password_test
223
+ end
224
+
225
+ def test_should_inherit_encrypted_attributes
226
+ assert_equal [User.encrypted_attributes.keys, :testing].flatten.collect { |key| key.to_s }.sort, Admin.encrypted_attributes.keys.collect { |key| key.to_s }.sort
227
+ end
228
+
229
+ def test_should_inherit_attr_encrypted_options
230
+ assert !User.attr_encrypted_options.empty?
231
+ assert_equal User.attr_encrypted_options, Admin.attr_encrypted_options
232
+ end
233
+
234
+ def test_should_not_inherit_unrelated_attributes
235
+ assert SomeOtherClass.attr_encrypted_options.empty?
236
+ assert SomeOtherClass.encrypted_attributes.empty?
237
+ end
238
+
239
+ def test_should_evaluate_a_symbol_option
240
+ assert_equal SomeOtherClass, SomeOtherClass.new.send(:evaluate_attr_encrypted_option, :class)
241
+ end
242
+
243
+ def test_should_evaluate_a_proc_option
244
+ assert_equal SomeOtherClass, SomeOtherClass.new.send(:evaluate_attr_encrypted_option, proc { |object| object.class })
245
+ end
246
+
247
+ def test_should_evaluate_a_lambda_option
248
+ assert_equal SomeOtherClass, SomeOtherClass.new.send(:evaluate_attr_encrypted_option, lambda { |object| object.class })
249
+ end
250
+
251
+ def test_should_evaluate_a_method_option
252
+ assert_equal SomeOtherClass, SomeOtherClass.new.send(:evaluate_attr_encrypted_option, SomeOtherClass.method(:call))
253
+ end
254
+
255
+ def test_should_return_a_string_option
256
+ class_string = 'SomeOtherClass'
257
+ assert_equal class_string, SomeOtherClass.new.send(:evaluate_attr_encrypted_option, class_string)
258
+ end
259
+
260
+ def test_should_encrypt_with_true_if
261
+ @user = User.new
262
+ assert_nil @user.encrypted_with_true_if
263
+ @user.with_true_if = 'testing'
264
+ refute_nil @user.encrypted_with_true_if
265
+ encrypted = Encryptor.encrypt(:value => 'testing', :key => SECRET_KEY, :iv => @user.encrypted_with_true_if_iv.unpack("m").first, :salt => @user.encrypted_with_true_if_salt.unpack("m").first)
266
+ assert_equal encrypted, @user.encrypted_with_true_if
267
+ end
268
+
269
+ def test_should_not_encrypt_with_false_if
270
+ @user = User.new
271
+ assert_nil @user.encrypted_with_false_if
272
+ @user.with_false_if = 'testing'
273
+ refute_nil @user.encrypted_with_false_if
274
+ assert_equal 'testing', @user.encrypted_with_false_if
275
+ end
276
+
277
+ def test_should_encrypt_with_false_unless
278
+ @user = User.new
279
+ assert_nil @user.encrypted_with_false_unless
280
+ @user.with_false_unless = 'testing'
281
+ refute_nil @user.encrypted_with_false_unless
282
+ encrypted = Encryptor.encrypt(:value => 'testing', :key => SECRET_KEY, :iv => @user.encrypted_with_false_unless_iv.unpack("m").first, :salt => @user.encrypted_with_false_unless_salt.unpack("m").first)
283
+ assert_equal encrypted, @user.encrypted_with_false_unless
284
+ end
285
+
286
+ def test_should_not_encrypt_with_true_unless
287
+ @user = User.new
288
+ assert_nil @user.encrypted_with_true_unless
289
+ @user.with_true_unless = 'testing'
290
+ refute_nil @user.encrypted_with_true_unless
291
+ assert_equal 'testing', @user.encrypted_with_true_unless
292
+ end
293
+
294
+ def test_should_encrypt_empty_with_truthy_allow_empty_value_option
295
+ @user = User.new
296
+ assert_nil @user.encrypted_with_allow_empty_value
297
+ @user.with_allow_empty_value = ''
298
+ refute_nil @user.encrypted_with_allow_empty_value
299
+ assert_equal '', @user.with_allow_empty_value
300
+ @user = User.new
301
+ @user.with_allow_empty_value = nil
302
+ refute_nil @user.encrypted_with_allow_empty_value
303
+ assert_nil @user.with_allow_empty_value
304
+ end
305
+
306
+ def test_should_work_with_aliased_attr_encryptor
307
+ assert User.encrypted_attributes.include?(:aliased)
308
+ end
309
+
310
+ def test_should_always_reset_options
311
+ @user = User.new
312
+ @user.with_if_changed = "encrypt_stuff"
313
+
314
+ @user = User.new
315
+ @user.should_encrypt = false
316
+ @user.with_if_changed = "not_encrypted_stuff"
317
+ assert_equal "not_encrypted_stuff", @user.with_if_changed
318
+ assert_equal "not_encrypted_stuff", @user.encrypted_with_if_changed
319
+ end
320
+
321
+ def test_should_cast_values_as_strings_before_encrypting
322
+ string_encrypted_email = User.encrypt_email('3', iv: @iv)
323
+ assert_equal string_encrypted_email, User.encrypt_email(3, iv: @iv)
324
+ assert_equal '3', User.decrypt_email(string_encrypted_email, iv: @iv)
325
+ end
326
+
327
+ def test_should_create_query_accessor
328
+ @user = User.new
329
+ assert !@user.email?
330
+ @user.email = ''
331
+ assert !@user.email?
332
+ @user.email = 'test@example.com'
333
+ assert @user.email?
334
+ end
335
+
336
+ def test_should_vary_iv_per_attribute
337
+ @user = User.new
338
+ @user.email = 'email@example.com'
339
+ @user.password = 'p455w0rd'
340
+ refute_equal @user.encrypted_email_iv, @user.crypted_password_test_iv
341
+ end
342
+
343
+ def test_should_generate_iv_per_attribute_by_default
344
+ thing = YetAnotherClass.new(email: 'thing@thing.com')
345
+ refute_nil thing.encrypted_email_iv
346
+ end
347
+
348
+ def test_should_vary_iv_per_instance
349
+ @user1 = User.new
350
+ @user1.email = 'email@example.com'
351
+ @user2 = User.new
352
+ @user2.email = 'email@example.com'
353
+ refute_equal @user1.encrypted_email_iv, @user2.encrypted_email_iv
354
+ refute_equal @user1.encrypted_email, @user2.encrypted_email
355
+ end
356
+
357
+ def test_should_vary_salt_per_attribute
358
+ @user = User.new
359
+ @user.email = 'email@example.com'
360
+ @user.password = 'p455w0rd'
361
+ refute_equal @user.encrypted_email_salt, @user.crypted_password_test_salt
362
+ end
363
+
364
+ def test_should_vary_salt_per_instance
365
+ @user1 = User.new
366
+ @user1.email = 'email@example.com'
367
+ @user2 = User.new
368
+ @user2.email = 'email@example.com'
369
+ refute_equal @user1.encrypted_email_salt, @user2.encrypted_email_salt
370
+ end
371
+
372
+ def test_should_not_generate_salt_per_attribute_by_default
373
+ thing = YetAnotherClass.new(email: 'thing@thing.com')
374
+ assert_nil thing.encrypted_email_salt
375
+ end
376
+
377
+ def test_should_decrypt_second_record
378
+ @user1 = User.new
379
+ @user1.email = 'test@example.com'
380
+
381
+ @user2 = User.new
382
+ @user2.email = 'test@example.com'
383
+
384
+ assert_equal 'test@example.com', @user1.decrypt(:email, @user1.encrypted_email)
385
+ end
386
+
387
+ def test_should_specify_the_default_algorithm
388
+ assert YetAnotherClass.encrypted_attributes[:email][:algorithm]
389
+ assert_equal YetAnotherClass.encrypted_attributes[:email][:algorithm], 'aes-256-gcm'
390
+ end
391
+
392
+ def test_should_not_encode_iv_when_encode_iv_is_false
393
+ email = 'thing@thing.com'
394
+ thing = YetAnotherClass.new(email: email)
395
+ refute thing.encrypted_email_iv =~ base64_encoding_regex
396
+ assert_equal thing.email, email
397
+ end
398
+
399
+ def test_should_base64_encode_iv_by_default
400
+ phone_number = '555-555-5555'
401
+ thing = YetAnotherClass.new
402
+ thing.phone_number = phone_number
403
+ assert thing.encrypted_phone_number_iv =~ base64_encoding_regex
404
+ assert_equal thing.phone_number, phone_number
405
+ end
406
+
407
+ def test_should_generate_unique_iv_for_every_encrypt_operation
408
+ user = User.new
409
+ user.email = 'initial_value@test.com'
410
+ original_iv = user.encrypted_email_iv
411
+ user.email = 'revised_value@test.com'
412
+ refute_equal original_iv, user.encrypted_email_iv
413
+ end
414
+
415
+ def test_should_not_generate_iv_for_attribute_when_if_option_is_false
416
+ user = User.new
417
+ user.with_false_if = 'derp'
418
+ assert_nil user.encrypted_with_false_if_iv
419
+ end
420
+
421
+ def test_should_generate_iv_for_attribute_when_if_option_is_true
422
+ user = User.new
423
+ user.with_true_if = 'derp'
424
+ refute_nil user.encrypted_with_true_if_iv
425
+
426
+ user.with_true_if = Object.new
427
+ refute_nil user.encrypted_with_true_if_iv
428
+ end
429
+
430
+ def test_should_not_generate_salt_for_attribute_when_if_option_is_false
431
+ user = User.new
432
+ user.with_false_if = 'derp'
433
+ assert_nil user.encrypted_with_false_if_salt
434
+ end
435
+
436
+ def test_should_generate_salt_for_attribute_when_if_option_is_true
437
+ user = User.new
438
+ user.with_true_if = 'derp'
439
+ refute_nil user.encrypted_with_true_if_salt
440
+ end
441
+
442
+ def test_should_generate_iv_for_attribute_when_unless_option_is_false
443
+ user = User.new
444
+ user.with_false_unless = 'derp'
445
+ refute_nil user.encrypted_with_false_unless_iv
446
+ end
447
+
448
+ def test_should_not_generate_iv_for_attribute_when_unless_option_is_true
449
+ user = User.new
450
+ user.with_true_unless = 'derp'
451
+ assert_nil user.encrypted_with_true_unless_iv
452
+ end
453
+
454
+ def test_should_generate_salt_for_attribute_when_unless_option_is_false
455
+ user = User.new
456
+ user.with_false_unless = 'derp'
457
+ refute_nil user.encrypted_with_false_unless_salt
458
+ end
459
+
460
+ def test_should_not_generate_salt_for_attribute_when_unless_option_is_true
461
+ user = User.new
462
+ user.with_true_unless = 'derp'
463
+ assert_nil user.encrypted_with_true_unless_salt
464
+ end
465
+
466
+ def test_should_not_by_default_generate_iv_when_attribute_is_empty
467
+ user = User.new
468
+ user.with_true_if = nil
469
+ assert_nil user.encrypted_with_true_if_iv
470
+ end
471
+
472
+ def test_encrypted_attributes_state_is_not_shared
473
+ user = User.new
474
+ user.ssn = '123456789'
475
+
476
+ another_user = User.new
477
+
478
+ assert_equal :encrypting, user.encrypted_attributes[:ssn][:operation]
479
+ assert_nil another_user.encrypted_attributes[:ssn][:operation]
480
+ end
481
+
482
+ def test_should_not_by_default_generate_key_when_attribute_is_empty
483
+ user = User.new
484
+ calls = 0
485
+ user.stub(:secret_key, lambda { calls += 1; SECRET_KEY }) do
486
+ user.ssn
487
+ end
488
+ assert_equal 0, calls
489
+ end
490
+ end