ar_database_duplicator 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ module ArDatabaseDuplicator
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,50 @@
1
+ ActiveRecord::Schema.define(:version => 20100610212120) do
2
+
3
+ create_table "users", :force => true do |t|
4
+ t.string "name", :limit => 123, :default => ""
5
+ t.string "email", :limit => 231
6
+ t.string "crypted_password", :limit => 80
7
+ t.string "salt", :limit => 124
8
+ t.datetime "created_at"
9
+ t.datetime "updated_at"
10
+ t.string "remember_token", :limit => 93
11
+ t.datetime "remember_token_expires_at"
12
+ t.integer "session_timeout", :default => 18, :null => false
13
+ t.string "login", :null => false
14
+ t.string "label"
15
+ t.string "first_name"
16
+ t.string "last_name"
17
+ t.string "activation_code"
18
+ t.datetime "activated_at"
19
+ t.boolean "agree_to_terms", :default => false, :null => false
20
+ t.string "zip_code"
21
+ t.string "phone_number"
22
+ t.string "work_number"
23
+ t.string "facebook_id"
24
+ t.integer "timezone"
25
+ t.string "token"
26
+ t.string "address_line_1"
27
+ t.string "address_line_2"
28
+ t.string "city"
29
+ t.string "state"
30
+ t.string "country"
31
+ t.integer "birth_year"
32
+ t.string "gender"
33
+ end
34
+
35
+ add_index "users", ["activation_code"], :name => "activation_code"
36
+ add_index "users", ["email"], :name => "email"
37
+ add_index "users", ["login"], :name => "login"
38
+ add_index "users", ["name"], :name => "name"
39
+ add_index "users", ["remember_token"], :name => "remember_token"
40
+
41
+ create_table "zip_codes", :force => true do |t|
42
+ t.string "zip"
43
+ t.string "city"
44
+ t.string "state", :limit => 2
45
+ t.float "latitude"
46
+ t.float "longitude"
47
+ end
48
+
49
+ end
50
+
@@ -0,0 +1,679 @@
1
+ require File.dirname(__FILE__) + "/../test_helper"
2
+ require 'fileutils'
3
+
4
+ class TestClass < ActiveRecord::Base
5
+ COLUMN_NAMES = ["safe", "temp_safe", "unsafe", "instance_safe", "changed"]
6
+
7
+ def self.column_names
8
+ COLUMN_NAMES
9
+ end
10
+
11
+ def initialize
12
+ end
13
+
14
+ end
15
+
16
+ class ARDatabaseDuplicatorTest < Test::Unit::TestCase
17
+ context "instance" do
18
+
19
+ context "public methods" do
20
+
21
+ setup do
22
+ Dir.chdir(Rails.root)
23
+ @db = ARDatabaseDuplicator.new(:source => "test_source", :destination => "test_destination")
24
+ ARDatabaseDuplicator.send(:public, :destination_directory_exists?)
25
+ end
26
+
27
+ teardown do
28
+ remove_duplications
29
+ end
30
+
31
+ should "call use_connection in use_source" do
32
+ @db.expects("use_connection").with(@db.source, "subname")
33
+ @db.use_source("subname")
34
+ end
35
+
36
+ should "call use_connection in use_destination" do
37
+ @db.expects("use_connection").with(@db.destination, "subname")
38
+ @db.use_destination("subname")
39
+ end
40
+
41
+ should "not allow destination=production" do
42
+ assert_raises(ArgumentError) do
43
+ @db.destination = "production"
44
+ end
45
+ end
46
+
47
+ should "set destination_directory_exists? to false after setting destination" do
48
+ @db.instance_variable_set(:@destination_directory_exists, true)
49
+ @db.destination = "staging"
50
+ assert !@db.destination_directory_exists?
51
+ assert_equal "staging", @db.destination
52
+ end
53
+
54
+ should "set destination_directory_exists? to false after setting split_data" do
55
+ @db.instance_variable_set(:@destination_directory_exists, true)
56
+ @db.split_data = true
57
+ assert !@db.destination_directory_exists?
58
+ assert @db.split_data
59
+ end
60
+
61
+ context "#load_duplication" do
62
+ should "not allow for production as a source" do
63
+ @db.source = 'Production'
64
+ assert_raises(ArgumentError) do
65
+ @db.load_duplication('User')
66
+ end
67
+ end
68
+ end
69
+
70
+ should "ensure SchemaMigration is defined" do
71
+ @db.stubs(:load_schema_split)
72
+ @db.stubs(:load_schema_combined)
73
+ Object.send(:remove_const, :SchemaMigration) if Object.const_defined?(:SchemaMigration)
74
+ assert !Object.const_defined?(:SchemaMigration)
75
+ @db.load_schema
76
+ assert Object.const_defined?(:SchemaMigration)
77
+ end
78
+
79
+ should "call load_schema_split when split_data? is true" do
80
+ @db.stubs(:load_schema_split)
81
+ @db.stubs(:load_schema_combined)
82
+ @db.split_data = true
83
+ @db.expects(:load_schema_split)
84
+ @db.load_schema
85
+ end
86
+
87
+ should "call load_schema_combined when split_data? is false" do
88
+ @db.stubs(:load_schema_split)
89
+ @db.stubs(:load_schema_combined)
90
+ @db.split_data = false
91
+ @db.expects(:load_schema_combined)
92
+ @db.load_schema
93
+ end
94
+
95
+ should "define AR when for the given name" do
96
+ Object.send(:remove_const, "Something") if Object.const_defined?("Something")
97
+ @db.define_class("something")
98
+ assert Object.const_defined?("Something")
99
+ assert(Something < ActiveRecord::Base)
100
+ end
101
+
102
+ context "#duplicate" do
103
+
104
+ setup do
105
+ Dir.chdir(Rails.root)
106
+ @db = ARDatabaseDuplicator.new(:schema_file => "db/sample_schema.rb", :source => Rails.root + "db/duplication/test_source", :destination => "test_destination")
107
+ @db.silent=true # Turn this to false if investigating issues
108
+ @db.with_source { load @db.schema_file }
109
+
110
+ @db.define_class("User")
111
+ @db.define_class("ZipCode")
112
+
113
+ @db.with_source do
114
+ 3.times { |i| User.create(:name => "User #{i}", :login => "user_#{i}", :phone_number => "111-2222", :work_number => "111-2222", :label => "whatever", :zip_code => "12345") }
115
+ 3.times { |i| ZipCode.create(:zip => ('%.6i' % i).to_s) }
116
+ end
117
+ end
118
+
119
+ should "duplicate the records from source to destination with anonymization" do
120
+ users = @db.with_source { User.all }
121
+ zips = @db.with_source { ZipCode.all }
122
+
123
+ assert_not_equal [], users, "No users found to test with."
124
+ assert_not_equal [], zips, "No zip codes found to test with."
125
+
126
+ @db.load_schema
127
+
128
+
129
+
130
+ zip_fields = ["zip", "city", "state", "latitude", "longitude"]
131
+ zip_fields.each { |field| ZipCode.mark_attribute_safe field }
132
+
133
+ @db.duplicate(ZipCode)
134
+ zips2 = @db.with_destination(ZipCode) { ZipCode.all }
135
+ assert_equal zips.size, zips2.size, "Missing zip codes after duplication"
136
+ assert_equal zips, zips2, "Data corruption when duplicating zip codes."
137
+
138
+
139
+ user_fields = ["crypted_password", "salt", "remember_token", "remember_token_expires_at",
140
+ "session_timeout", "login", "first_name", "last_name", "activation_code", "activated_at",
141
+ "agree_to_terms", "facebook_id", "timezone", "token", "address_line_1", "address_line_2",
142
+ "city", "state", "country", "birth_year", "gender"]
143
+ user_fields.each { |field| User.mark_attribute_safe field }
144
+
145
+ @db.duplicate(User, {:name => :full_name,
146
+ :email => :email_address,
147
+ :phone_number => :phone_number
148
+ }, {
149
+ :work_number => :phone_number,
150
+ :label => lambda { rand(1234).to_s }
151
+ }) do |random|
152
+ hash = {}
153
+ hash[:zip_code] = random.zip_code
154
+ hash
155
+ end
156
+
157
+ users2 = @db.with_destination(User) { User.all }
158
+ assert_equal users.size, users2.size, "Missing users after duplication"
159
+
160
+ users.each_with_index do |user, i|
161
+ user2 = users2[i]
162
+ assert_not_equal user.name, user2.name, "User name was not replaced" if user.name
163
+ assert_not_equal user.email, user2.email, "User email was not replaced" if user.email
164
+ assert_not_equal user.phone_number, user2.phone_number, "User phone_number was not replaced" if user.phone_number
165
+ assert_not_equal user.work_number, user2.work_number, "User work_number was not replaced" if user.work_number
166
+ assert_not_equal user.label, user2.label, "User name was not replaced" if user.label
167
+ assert_not_equal user.zip_code, user2.zip_code, "User zip_code was not replaced" if user.zip_code
168
+
169
+ assert_equal user.login, user2.login, "User login was corrupted during duplication"
170
+ assert_not_equal user2.phone_number, user2.work_number, "Duplication did not create different phone and work numbers."
171
+ end
172
+
173
+ end
174
+
175
+ end
176
+
177
+ should "call with_connection using source in with_source" do
178
+ @db.expects("with_connection").with(@db.source, "subname", true)
179
+ @db.with_source("subname", true) {}
180
+ end
181
+
182
+ should "call with_connection using destination in with_destination" do
183
+ @db.expects("with_connection").with(@db.destination, "subname", true)
184
+ @db.with_destination("subname", true) {}
185
+ end
186
+
187
+ end
188
+
189
+ context "private methods" do
190
+
191
+ setup do
192
+ Dir.chdir(Rails.root)
193
+ @db = ARDatabaseDuplicator.new(:schema_file => "db/sample_schema.rb", :source => "test_source", :destination => "test_destination")
194
+ @db.silent=true
195
+
196
+ ARDatabaseDuplicator.send(:public, :destination_directory)
197
+ ARDatabaseDuplicator.send(:public, :load_schema_combined)
198
+ ARDatabaseDuplicator.send(:public, :load_schema_split)
199
+ ARDatabaseDuplicator.send(:public, :replace_attributes)
200
+ ARDatabaseDuplicator.send(:public, :replace)
201
+ ARDatabaseDuplicator.send(:public, :replace_with)
202
+
203
+ ARDatabaseDuplicator.send(:public, :base_path)
204
+ ARDatabaseDuplicator.send(:public, :entity)
205
+ end
206
+
207
+ teardown do
208
+ remove_duplications
209
+ end
210
+
211
+ context "#replace" do
212
+ setup do
213
+ @db.stubs(:replace_with)
214
+ end
215
+
216
+ should "get the replacement value from PseudoEntity if the value is a symbol" do
217
+ first_name = @db.entity.first_name
218
+ @db.entity.instance_eval <<-EOF
219
+ alias :first_name_original :first_name
220
+ EOF
221
+ @db.entity.expects(:first_name).once.returns(@db.entity.first_name_original)
222
+ assert @db.expects(:replace_with).with(nil, "something", first_name)
223
+ @db.replace(nil, {"something" => :first_name})
224
+ end
225
+
226
+ should "raise an exception if the entity does not respond to the symbol value" do
227
+ assert_raises(RuntimeError) do
228
+ @db.replace(nil, {"something" => :doesnt_exist})
229
+ end
230
+ end
231
+
232
+ should "not call on PseudoEntity if the value is not a symbol" do
233
+ @db.entity.expects(:first_name).never
234
+ assert @db.expects(:replace_with).with(nil, "something", "first_name")
235
+ @db.replace(nil, {"something" => "first_name"})
236
+ end
237
+
238
+ should "call the original PseudoEntity method when encryption is asked for" do
239
+ first_name = @db.entity.first_name
240
+ @db.entity.instance_eval <<-EOF
241
+ alias :first_name_original :first_name
242
+ EOF
243
+ @db.entity.expects(:first_name).once.returns(@db.entity.first_name_original)
244
+ assert @db.expects(:replace_with).with(nil, "something", first_name.encrypt(:key => "1234"))
245
+ @db.replace(nil, {"something" => :encrypted_first_name})
246
+ end
247
+
248
+ should "set iv and salt if the target responds to them for encrypted values" do
249
+ @db = ARDatabaseDuplicator.new
250
+ @db.silent=true
251
+ first_name = @db.entity.first_name
252
+ object = Object.new
253
+ class << object
254
+ def first_name
255
+ @first_name ||= "a"
256
+ end
257
+ def first_name_salt
258
+ @first_name_salt ||= "a"
259
+ end
260
+ def first_name_iv
261
+ @first_name_iv ||= "a"
262
+ end
263
+ def first_name=(x)
264
+ @first_name = x
265
+ end
266
+ def first_name_salt=(x)
267
+ @first_name_salt = x
268
+ end
269
+ def first_name_iv=(x)
270
+ @first_name_iv = x
271
+ end
272
+ end
273
+ @db.replace(object, {:first_name => :encrypted_first_name})
274
+ assert_not_equal 1, object.first_name_salt, "Salt was not set"
275
+ assert_not_equal 1, object.first_name_iv, "Initialization vector was not set"
276
+ assert_equal first_name.encrypt(:key => "1234", :iv => object.first_name_iv, :salt => object.first_name_salt), object.first_name
277
+ end
278
+
279
+
280
+ end
281
+
282
+ context "replace_attributes" do
283
+ setup do
284
+ @db.stubs(:replace)
285
+ end
286
+
287
+ should "call entity.reset! once before replace" do
288
+ name = @db.entity.first_name
289
+ other_name = name
290
+
291
+ singleton_klass = class << @db; self; end
292
+ singleton_klass.send(:define_method, :replace) { |*args|
293
+ other_name = entity.first_name
294
+ }
295
+
296
+ @db.replace_attributes(nil, [{:a => 1}])
297
+ assert name != other_name, "entity.reset! was not called before replace"
298
+ end
299
+
300
+ should "not call replace when replacement_hash empty" do
301
+ @db.expects(:replace).never()
302
+ @db.replace_attributes(nil, [{}])
303
+ end
304
+
305
+ should "yield entity when block given with arity==1" do
306
+ name = @db.entity.first_name
307
+ @db.replace_attributes(nil, []) do |x|
308
+ assert x.is_a?(PseudoEntity)
309
+ assert x.first_name != name
310
+ end
311
+ end
312
+
313
+ should "yield entity, record when block given with arity != 1" do
314
+ name = @db.entity.first_name
315
+ @db.replace_attributes("abc", []) do |x, y|
316
+ assert x.is_a?(PseudoEntity)
317
+ assert x.first_name != name
318
+ assert_equal "abc", y
319
+ end
320
+ end
321
+
322
+ should "call replace if block returns a hash" do
323
+ @db.expects(:replace).once()
324
+ @db.replace_attributes(nil, []) { |x| {:a => 1} }
325
+ end
326
+
327
+ should "not call replace if block does not return a hash" do
328
+ @db.expects(:replace).never()
329
+ @db.replace_attributes(nil, []) { |x| nil }
330
+ end
331
+ end
332
+
333
+ context "#replace_with" do
334
+
335
+ def setup
336
+ @object = Object.new.tap do |o|
337
+ class << o
338
+ def first_name
339
+ @first_name ||= "John"
340
+ end
341
+ def first_name=(x)
342
+ @first_name = x
343
+ end
344
+ end
345
+ end
346
+ end
347
+
348
+ should "use the setter in the target to assign the value" do
349
+ @db.replace_with(@object, :first_name, "test")
350
+ assert_equal "test", @object.first_name
351
+ end
352
+
353
+ should "should use the setter in the target to assign the value if the getter returns a blank value" do
354
+ @object.first_name = ""
355
+ @db.replace_with(@object, :first_name, "test")
356
+ assert_equal '', @object.first_name
357
+ end
358
+
359
+ should "evaluate a proc if given as the value" do
360
+ proc = Proc.new { "Jack" }
361
+ @db.replace_with(@object, :first_name, proc)
362
+ assert_equal "Jack", @object.first_name
363
+ end
364
+
365
+ should "pass entity, target, and key to proc depending on arity" do
366
+ proc0 = Proc.new {}
367
+ proc1 = Proc.new { |entity| assert_equal @db.entity, entity }
368
+ proc2 = Proc.new { |entity, target| assert_equal [@db.entity, @object], [entity, target]}
369
+ proc3 = Proc.new { |entity, target, key| assert_equal [@db.entity, @object, :first_name], [entity, target, key] }
370
+
371
+ @db.replace_with(@object, :first_name, proc0)
372
+ @db.replace_with(@object, :first_name, proc1)
373
+ @db.replace_with(@object, :first_name, proc2)
374
+ @db.replace_with(@object, :first_name, proc3)
375
+ end
376
+
377
+ end
378
+
379
+ context "#destination_directory" do
380
+
381
+ should "use only the base_path if split_data is false" do
382
+ @db.split_data = false
383
+ assert_equal @db.base_path, @db.destination_directory
384
+ end
385
+
386
+ should "use the base_path and the destination if split_data is true" do
387
+ @db.split_data = true
388
+ assert_equal (@db.base_path + @db.destination), @db.destination_directory
389
+ end
390
+
391
+ end
392
+
393
+ context "#load_schema_combined" do
394
+
395
+ setup do
396
+ @db.load_schema_combined
397
+ end
398
+
399
+ should "migrate the schema" do
400
+ @db.with_destination do
401
+ @db.define_class('SchemaMigration')
402
+ assert SchemaMigration.table_exists?, "SchemaMigration table does not exist"
403
+ assert_operator SchemaMigration.count, :>, 0
404
+ end
405
+ end
406
+
407
+ should "create the table schema" do
408
+ @db.with_destination do
409
+ @db.define_class('TableSchema')
410
+ assert TableSchema.table_exists?, "TableSchema table does not exist"
411
+ assert_operator TableSchema.count, :>, 0
412
+ end
413
+ end
414
+
415
+ should "store the schema for each table" do
416
+ @db.with_destination do
417
+ captured_schema = ARDatabaseDuplicator::CapturedSchema.new(@db, @db.schema_file)
418
+ captured_schema.table_names.each do |table_name|
419
+ table_schema = TableSchema.find(:first, :conditions => {:table_name => table_name})
420
+ assert table_schema, "Missing table schema for #{table_name}"
421
+ assert_equal captured_schema.schema_for(table_name), table_schema.schema
422
+ end
423
+ end
424
+ end
425
+
426
+ end
427
+
428
+
429
+ context "#load_schema_split" do
430
+
431
+ setup do
432
+ @db.load_schema_split
433
+ @captured_schema = ARDatabaseDuplicator::CapturedSchema.new(@db, @db.schema_file)
434
+ end
435
+
436
+ should "migrate the schema" do
437
+ @captured_schema.table_names.each do |table_name|
438
+ @db.with_destination(table_name, true) do
439
+ @db.define_class('SchemaMigration')
440
+ assert SchemaMigration.table_exists?, "SchemaMigration table does not exist in #{table_name}"
441
+ assert_operator SchemaMigration.count, :>, 0
442
+ end
443
+ end
444
+ end
445
+
446
+ should "create the table schema" do
447
+ @db.define_class('TableSchema')
448
+ @captured_schema.table_names.each do |table_name|
449
+ @db.with_destination(table_name, true) do
450
+ assert TableSchema.table_exists?, "TableSchema table does not exist in #{table_name}"
451
+ assert_operator TableSchema.count, :>, 0
452
+ end
453
+ end
454
+ end
455
+
456
+ should "store the schema for each table" do
457
+ @captured_schema.table_names.each do |table_name|
458
+ @db.with_destination(table_name, true) do
459
+ table_schema = TableSchema.find(:first, :conditions => {:table_name => table_name})
460
+ assert table_schema, "Missing table schema for #{table_name}"
461
+ assert_equal @captured_schema.schema_for(table_name), table_schema.schema
462
+ end
463
+ end
464
+ end
465
+
466
+ end
467
+
468
+
469
+ end
470
+
471
+
472
+
473
+ end
474
+
475
+ context "class" do
476
+ context "instance" do
477
+ should "return a new instance" do
478
+ assert ARDatabaseDuplicator.instance.is_a?(ARDatabaseDuplicator)
479
+ end
480
+
481
+ should "return the same instance" do
482
+ first = ARDatabaseDuplicator.instance
483
+ assert first.equal?(ARDatabaseDuplicator.instance)
484
+ end
485
+
486
+ end
487
+
488
+ context "reset" do
489
+ should "cause instance to return a new instance" do
490
+ first = ARDatabaseDuplicator.instance
491
+ ARDatabaseDuplicator.reset!
492
+ assert !first.equal?(ARDatabaseDuplicator.instance)
493
+ end
494
+ end
495
+
496
+ end
497
+
498
+ end
499
+
500
+ class VettedRecordTest < Test::Unit::TestCase
501
+ context "class" do
502
+ context "field_vetting" do
503
+ setup do
504
+ TestClass.instance_variable_set(:@field_vetting, nil)
505
+ end
506
+
507
+ should "default to true when created" do
508
+ assert TestClass.field_vetting
509
+ end
510
+
511
+ should "default to true if set to nil" do
512
+ TestClass.field_vetting = nil
513
+ assert TestClass.field_vetting
514
+ end
515
+
516
+ should "remain false when set to false" do
517
+ TestClass.field_vetting = false
518
+ assert !TestClass.field_vetting
519
+ end
520
+ end
521
+
522
+ context "safe_attributes" do
523
+ setup do
524
+ TestClass.instance_variable_set(:@safe_attributes, nil)
525
+ end
526
+
527
+ should "default to empty array" do
528
+ assert TestClass.safe_attributes.blank?
529
+ end
530
+
531
+ should "be able to add to safe_attributes" do
532
+ TestClass.mark_attribute_safe("safe")
533
+ assert_equal ["safe"], TestClass.safe_attributes
534
+ end
535
+
536
+ should "not allow duplicates" do
537
+ TestClass.mark_attribute_safe("safe")
538
+ TestClass.mark_attribute_safe("safe")
539
+ assert_equal ["safe"], TestClass.safe_attributes
540
+ end
541
+ end
542
+
543
+ context "temporary_safe_attributes" do
544
+ setup do
545
+ TestClass.clear_temporary_safe_attributes
546
+ end
547
+
548
+ should "clear temp safe attributes" do
549
+ TestClass.mark_attribute_temporarily_safe("new_one")
550
+ TestClass.clear_temporary_safe_attributes
551
+ assert TestClass.temporary_safe_attributes.blank?
552
+ end
553
+
554
+ should "default to empty array" do
555
+ assert TestClass.temporary_safe_attributes.blank?
556
+ end
557
+
558
+ should "be able to add to safe_attributes" do
559
+ TestClass.mark_attribute_temporarily_safe("temp_safe")
560
+ assert_equal ["temp_safe"], TestClass.temporary_safe_attributes
561
+ end
562
+
563
+ should "not allow duplicates" do
564
+ TestClass.mark_attribute_temporarily_safe("temp_safe")
565
+ TestClass.mark_attribute_temporarily_safe("temp_safe")
566
+ assert_equal ["temp_safe"], TestClass.temporary_safe_attributes
567
+ end
568
+
569
+ end
570
+
571
+ context "vetted attributes" do
572
+ setup do
573
+ TestClass.instance_variable_set(:@safe_attributes, nil)
574
+ TestClass.clear_temporary_safe_attributes
575
+ end
576
+
577
+ should "return column_names if field_vetting false" do
578
+ TestClass.field_vetting = false
579
+
580
+ TestClass.mark_attribute_safe("safe")
581
+ TestClass.mark_attribute_temporarily_safe("temp_safe")
582
+ assert_equal TestClass::COLUMN_NAMES, TestClass.vetted_attributes
583
+ end
584
+
585
+ should "return only vetted attributes if field_vetting true" do
586
+ TestClass.field_vetting = true
587
+
588
+ TestClass.mark_attribute_safe("safe")
589
+ TestClass.mark_attribute_temporarily_safe("temp_safe")
590
+ assert_equal ["safe", "temp_safe"], TestClass.vetted_attributes
591
+ end
592
+
593
+ end
594
+
595
+ context "unvetted_attributes" do
596
+ setup do
597
+ TestClass.instance_variable_set(:@safe_attributes, nil)
598
+ TestClass.clear_temporary_safe_attributes
599
+ end
600
+
601
+ should "return a blank array if field_vetting false" do
602
+ TestClass.field_vetting = false
603
+
604
+ TestClass.mark_attribute_safe("safe")
605
+ TestClass.mark_attribute_temporarily_safe("temp_safe")
606
+ assert_equal [], TestClass.unvetted_attributes
607
+ end
608
+
609
+ should "return only unvetted attributes if field_vetting true" do
610
+ TestClass.field_vetting = true
611
+
612
+ TestClass.mark_attribute_safe("safe")
613
+ TestClass.mark_attribute_temporarily_safe("temp_safe")
614
+ assert_equal ["unsafe", "instance_safe", "changed"], TestClass.unvetted_attributes
615
+ end
616
+
617
+ end
618
+ end
619
+
620
+ context "instance" do
621
+ context "vetted attributes" do
622
+ setup do
623
+ @ar_class = TestClass.new
624
+ end
625
+
626
+ should "default to empty array " do
627
+ assert @ar_class.vetted_attributes.blank?
628
+ end
629
+
630
+ should "be able to add" do
631
+ @ar_class.vet_attribute("instance_safe")
632
+ assert_equal ["instance_safe"], @ar_class.vetted_attributes
633
+ end
634
+
635
+ should "not add duplicates" do
636
+ @ar_class.vet_attribute("instance_safe")
637
+ @ar_class.vet_attribute("instance_safe")
638
+ assert_equal ["instance_safe"], @ar_class.vetted_attributes
639
+ end
640
+ end
641
+
642
+ context "unvetted attributes" do
643
+
644
+ setup do
645
+ @ar_class = TestClass.new
646
+ end
647
+
648
+ should "return unvetted attributes" do
649
+ TestClass.mark_attribute_safe("safe")
650
+ TestClass.mark_attribute_temporarily_safe("temp_safe")
651
+ @ar_class.vet_attribute("instance_safe")
652
+ @ar_class.stubs(:changed_attributes).returns({"changed" => "changed"})
653
+ assert_equal ["unsafe"], @ar_class.unvetted_attributes
654
+ end
655
+
656
+ end
657
+
658
+ context "vetted_save" do
659
+ setup do
660
+ @ar_class = TestClass.new
661
+ @ar_class.stubs(:save_without_validation)
662
+ end
663
+
664
+ should "raise an exception if any of the attributes is not vetted" do
665
+ TestClass.field_vetting = true
666
+ assert_raises(ActiveRecord::VettedRecord::UnvettedAttribute) do
667
+ @ar_class.vetted_save
668
+ end
669
+ end
670
+
671
+ should "call save_without_validation if all attributes are vetted" do
672
+ @ar_class.expects(:save_without_validation).once
673
+ TestClass.field_vetting = false
674
+ @ar_class.vetted_save
675
+ end
676
+ end
677
+ end
678
+ end
679
+