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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +11 -0
- data/ar_database_duplicator.gemspec +36 -0
- data/lib/ar_database_duplicator.rb +834 -0
- data/lib/ar_database_duplicator/version.rb +3 -0
- data/test/db/sample_schema.rb +50 -0
- data/test/lib/ar_database_duplicator_test.rb +679 -0
- data/test/test_helper.rb +24 -0
- metadata +214 -0
|
@@ -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
|
+
|