ar_database_duplicator 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+