hover-ruby-client 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,8 @@
1
+ require './test/test_helper'
2
+
3
+ require 'hover/decoder/json_stream'
4
+
5
+ describe Hover::Decoder::JSONStream do
6
+ let(:instance) { Hover::Decoder::JSONStream.new }
7
+
8
+ end
@@ -0,0 +1,135 @@
1
+ require './test/test_helper'
2
+
3
+ require 'hover/encoder/json_stream'
4
+
5
+ describe Hover::Encoder::JSONStream do
6
+ let(:instance) { Hover::Encoder::JSONStream.new }
7
+
8
+ let(:attributes) do
9
+ {'a' => 1, 'b' => 2}
10
+ end
11
+
12
+ it "initializes" do
13
+ instance.must_be_kind_of(Hover::Encoder::JSONStream)
14
+ end
15
+
16
+ describe "open" do
17
+ it "sets file" do
18
+ file = mock
19
+ File.expects(:open).with("/tmp/filename", 'w').returns(file)
20
+
21
+ instance.open("/tmp/filename")
22
+ instance.file.must_equal(file)
23
+ end
24
+ end
25
+
26
+ describe "close" do
27
+ it "closes file" do
28
+ file = mock
29
+ file.expects(:close)
30
+
31
+ instance.file = file
32
+ instance.close
33
+
34
+ assert_nil instance.file
35
+ end
36
+ end
37
+
38
+ describe "append" do
39
+ it "appends" do
40
+ file = mock
41
+ file.expects(:puts).with("#{attributes.merge({'class' => 'Hash'}).to_json}")
42
+
43
+ instance.file = file
44
+
45
+ instance.append(attributes, attributes.class)
46
+ end
47
+
48
+ it "last" do
49
+ file = mock
50
+ file.expects(:puts).with("#{attributes.merge({'class' => 'Hash'}).to_json}")
51
+
52
+ instance.file = file
53
+
54
+ instance.append(attributes, attributes.class)
55
+ end
56
+ end
57
+
58
+ describe "append_each" do
59
+ it "appends" do
60
+ file = mock
61
+ file.expects(:puts).with("#{attributes.merge({'class' => 'Hash'}).to_json}").twice
62
+
63
+ instance.file = file
64
+
65
+ instance.append_each([attributes, attributes], attributes.class)
66
+ end
67
+ end
68
+
69
+ describe "append_records" do
70
+ it "appends from scope" do
71
+ TestClass.destroy_all
72
+
73
+ a = TestClass.create!
74
+ b = TestClass.create!
75
+
76
+ instance.expects(:append).with(a.attributes, TestClass)
77
+ instance.expects(:append).with(b.attributes, TestClass)
78
+
79
+ instance.append_records(TestClass.all)
80
+ end
81
+ end
82
+
83
+ describe "valid json" do
84
+
85
+ it "avoids NaN problem" do
86
+ # with PostgreSQL we could use TestClass#a, but sqlite persists the NaN as nil
87
+ class SpecialStandInForThisTest
88
+ def find_each
89
+ yield self
90
+ end
91
+ def attributes
92
+ # if JSONStream doesn't turn this NaN into a nil we'll see an error in parsing the generated JSON
93
+ nan = 0.0 / 0.0
94
+ {"foo" => "bar", "x" => nan}
95
+ end
96
+ end
97
+ file = Tempfile.new(['hover-ruby-client_json_encoder-NaN', '.json'])
98
+ instance.open(file.path)
99
+ instance.append_records(SpecialStandInForThisTest.new)
100
+ instance.close
101
+ JSON.parse(file.read).must_equal({"foo" => "bar", "x" => nil, "class"=>"SpecialStandInForThisTest"})
102
+ end
103
+
104
+ it "works" do
105
+ file = Tempfile.new(['hover-ruby-client_json_encoder', '.json'])
106
+
107
+ begin
108
+ 1.upto(50) do
109
+ TestClass.create!
110
+ end
111
+
112
+ 1.upto(50) do
113
+ Tag.create!
114
+ end
115
+
116
+ instance.open(file.path)
117
+ instance.append_records(TestClass.all)
118
+ instance.append_records(Tag.all)
119
+ instance.close
120
+
121
+ expected = [
122
+ TestClass.all.map { |test_class| test_class.attributes.merge({'class' => 'TestClass'}) },
123
+ Tag.all.map { |tag| tag.attributes.merge({'class' => 'Tag'}) },
124
+ ].flatten
125
+
126
+ file.each_line do |line|
127
+ expected.include?(JSON.parse(line)).must_equal(true)
128
+ end
129
+ ensure
130
+ file.close
131
+ file.unlink
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,354 @@
1
+ require './test/test_helper'
2
+
3
+ require 'hover/importer/active_record'
4
+ require 'active_record'
5
+
6
+ class TestClass < ActiveRecord::Base
7
+ belongs_to :item
8
+
9
+ def self.s3_file_versions
10
+ {image: {small: {}}, document: {}}
11
+ end
12
+
13
+ def document_s3_keys=(value)
14
+ value
15
+ end
16
+
17
+ def image_s3_keys=(value)
18
+ value
19
+ end
20
+ end
21
+
22
+ class ScopedTestClass < ActiveRecord::Base
23
+ belongs_to :item
24
+
25
+ default_scope -> { where(a: 1) }
26
+ end
27
+
28
+ class Item < ActiveRecord::Base
29
+ has_many :tags, as: :taggable, dependent: :destroy
30
+ end
31
+
32
+ class Tag < ActiveRecord::Base
33
+ belongs_to :taggable, polymorphic: true
34
+ end
35
+
36
+ describe Hover::Importer::ActiveRecord do
37
+ let(:record) { TestClass.new }
38
+ let(:update_finder) { ->(importer, klass, attributes) { record } }
39
+ let(:remote_local_map) { {table_name: {'remote_id' => 'local_id'}} }
40
+ let(:importer) { Hover::Importer::ActiveRecord.new(remote_local_map, update_finder) }
41
+
42
+ let(:results) do
43
+ {
44
+ "state" => "complete",
45
+ "results" => {
46
+ "test_classes" => [{"id" => 1}, {"id" => 2}]
47
+ }
48
+ }
49
+ end
50
+
51
+ describe "initialize" do
52
+ it "sets initial values" do
53
+ importer.remote_local_map.must_equal(remote_local_map)
54
+ assert_equal importer.update_finder, update_finder
55
+ importer.column_changes.must_equal({})
56
+ end
57
+ end
58
+
59
+ describe "update" do
60
+ it "calls update_record for each" do
61
+ importer.expects(:update_record).with(TestClass, {"id" => 1}).once
62
+ importer.expects(:update_record).with(TestClass, {"id" => 2}).once
63
+
64
+ importer.update("test_classes", results["results"].delete("test_classes"))
65
+ end
66
+
67
+ it "returns updated records" do
68
+ importer.expects(:update_record).twice.returns([TestClass, 1])
69
+ importer.update("test_classes", results["results"].delete("test_classes")).must_equal([[TestClass, 1], [TestClass, 1]])
70
+ end
71
+ end
72
+
73
+ describe "update_record" do
74
+ it "does nothing if the record to update is not found" do
75
+ importer.expects(:find_record_to_update).returns(nil)
76
+ assert_nil importer.update_record(TestClass, mock)
77
+ end
78
+
79
+ describe "saves" do
80
+ before do
81
+ end
82
+
83
+ it "does not update primary key and doesnt overwrite s3 keys" do
84
+ record.expects(:assign_attributes).with({"image_s3_keys" => nil, "document_s3_keys" => nil}).never
85
+ record.expects(:assign_attributes).with({}).once
86
+ importer.update_record(TestClass, {"id" => 1})
87
+ end
88
+
89
+ it "handles s3 file attachments" do
90
+ image_s3_key = {"small" => 100}
91
+ record.expects(:assign_attributes).with({"image_s3_keys" => image_s3_key}).once
92
+ importer.update_record(TestClass, {"id" => 1, "image" => image_s3_key})
93
+ end
94
+
95
+ it "excludes invalid column names" do
96
+ record.expects(:assign_attributes).with({}).once
97
+ record.expects(:assign_attributes).with({"image_s3_keys" => nil, "document_s3_keys" => nil}).never
98
+ importer.update_record(TestClass, {"id" => 1, "c" => 2})
99
+ end
100
+
101
+ it "includes valid column names" do
102
+ record.expects(:assign_attributes).with({"image_s3_keys" => nil, "document_s3_keys" => nil, "a" => 100}).never
103
+ record.expects(:assign_attributes).with({"a" => 100}).once
104
+ importer.update_record(TestClass, {"id" => 1, "a" => 100})
105
+ end
106
+
107
+ it "returns record" do
108
+ record.expects(:assign_attributes).with({"image_s3_keys" => nil, "document_s3_keys" => nil}).never
109
+ record.expects(:assign_attributes).with({}).once
110
+ importer.update_record(TestClass, {"id" => 1}).must_equal([TestClass, record.id])
111
+ end
112
+ end
113
+ end
114
+
115
+ describe "s3_file_keys" do
116
+ it "returns nil if klass isn't using s3 file" do
117
+ assert_nil importer.s3_file_keys(Integer, {})
118
+ end
119
+
120
+ it "skips other attributes" do
121
+ attributes = {"foo" => 1, 'document' => 2}
122
+ importer.s3_file_keys(TestClass, attributes).must_equal({'document_s3_keys' => 2})
123
+ end
124
+
125
+ it "does not default to nil" do
126
+ importer.s3_file_keys(TestClass, {}).must_equal({})
127
+ end
128
+
129
+ it "renames s3 attributes" do
130
+ attributes = {"image" => 1}
131
+ importer.s3_file_keys(TestClass, attributes).must_equal({"image_s3_keys" => 1})
132
+ end
133
+
134
+ it "assigns incoming keys correctly" do
135
+
136
+ attributes = {
137
+ "id" => 1,
138
+ "image" => {
139
+ "image" => "Midas/Image/236/image.jpg",
140
+ "image_small" => "Midas/Image/236/image_small.jpg",
141
+ "image_medium" => "Midas/Image/236/image_medium.jpg",
142
+ "image_low_quality" => "Midas/Image/236/image_low_quality.jpg",
143
+ "image_thumb" => "Manowar/Image/172/image_thumb.jpg"
144
+ }
145
+ }
146
+
147
+ importer.expects(:insert_record).once.with(record).returns(2)
148
+ TestClass.expects(:new).with({"image_s3_keys" => attributes["image"]}).returns(record)
149
+
150
+ importer.import_record(TestClass, attributes).must_equal([TestClass, 2])
151
+
152
+ importer.remote_local_map["test_classes"][1].must_equal(2)
153
+ end
154
+ end
155
+
156
+ describe "find_record_to_update" do
157
+ it "calls update_finder" do
158
+ update_finder.expects(:call).with(importer, TestClass, {a: 1})
159
+ importer.find_record_to_update(TestClass, {a: 1})
160
+ end
161
+ end
162
+
163
+ describe "import_results" do
164
+ it "imports" do
165
+ importer.expects(:import_table).with("test_classes", [{"id" => 1}, {"id" => 2}]).once
166
+ importer.import_results(results["results"])
167
+ end
168
+
169
+ it "returns [class, id] sets for imported records" do
170
+ importer.expects(:import_record).returns([record.class, record.id]).twice
171
+ importer.import_results(results["results"]).must_equal([[TestClass, record.id], [TestClass, record.id]])
172
+ end
173
+ end
174
+
175
+ describe "import_table" do
176
+ it "imports" do
177
+ importer.expects(:import_record).with(TestClass, {"id" => 1}).once
178
+ importer.import_table("test_classes", [{"id" => 1}])
179
+ end
180
+
181
+ it "returns [class, id] sets for imported records" do
182
+ importer.expects(:import_record).returns([record.class, record.id]).twice
183
+ importer.import_table("test_classes", [{}, {}]).must_equal([[TestClass, record.id], [TestClass, record.id]])
184
+ end
185
+ end
186
+
187
+ describe "import_record" do
188
+ it "returns nil if insert fails" do
189
+ importer.expects(:insert_record).once.returns(nil)
190
+ TestClass.expects(:find).never
191
+ assert_nil importer.import_record(TestClass, {})
192
+ end
193
+
194
+ it "imports" do
195
+ importer.expects(:insert_record).once.with(record).returns(2)
196
+ TestClass.expects(:new).with({}).returns(record)
197
+
198
+ importer.import_record(TestClass, {"id" => 1}).must_equal([TestClass, 2])
199
+
200
+ importer.remote_local_map["test_classes"][1].must_equal(2)
201
+ end
202
+
203
+ it "filters out attributes that don't have corresponding columns in the local database" do
204
+ class TestClass
205
+ def attributes
206
+ super.merge('upstream_attribute' => 1)
207
+ end
208
+ end
209
+
210
+ starting_count = TestClass.count
211
+ importer.import_record(TestClass, {})
212
+ assert_equal (starting_count + 1), TestClass.count
213
+ end
214
+ end
215
+
216
+ describe "insert_record" do
217
+ it "inserts" do
218
+ TestClass.expects(:_insert_record).returns(1)
219
+
220
+ attributes = {"id" => 33}
221
+ record = TestClass.new(attributes)
222
+
223
+ importer.insert_record(record).must_equal(1)
224
+ end
225
+
226
+
227
+ end
228
+
229
+ describe "new_foreign_keys" do
230
+ it do
231
+ record.item_id = 2
232
+ importer.remote_local_map["items"] = {2 => 1}
233
+
234
+ importer.new_foreign_keys(record).must_equal({"item_id" => 1})
235
+ end
236
+
237
+ it "processes belongs_to macro" do
238
+ record.item_id = 2
239
+ association = record.class.reflect_on_all_associations.first
240
+ importer.expects(:new_foreign_keys_for_belongs_to).with(record, association)
241
+ importer.new_foreign_keys(record)
242
+ end
243
+
244
+ it "processes polymorphic belongs_to macro" do
245
+ tag = Tag.create!(name: 'tag', taggable_type: Item.name, taggable_id: 1)
246
+ association = tag.class.reflect_on_all_associations.first
247
+ importer.expects(:new_foreign_keys_for_belongs_to_polymorphic).with(tag, association)
248
+ importer.new_foreign_keys(tag)
249
+ end
250
+
251
+ it "supports polymorphic relationships" do
252
+ tag = Tag.create!(name: 'tag', taggable_type: Item.name, taggable_id: 1)
253
+ importer.remote_local_map["items"] = {1 => 3}
254
+ importer.new_foreign_keys(tag).must_equal({"taggable_id" => 3})
255
+ end
256
+ end
257
+
258
+ describe "new_foreign_keys_belongs_to" do
259
+ it do
260
+ record.item_id = 2
261
+ importer.remote_local_map["items"] = {2 => 1}
262
+ association = record.class.reflect_on_all_associations.first
263
+
264
+ importer.new_foreign_keys_for_belongs_to(record, association).must_equal(['item_id', 1])
265
+ end
266
+ end
267
+
268
+ describe "new_foreign_keys_belongs_to_polymorphic" do
269
+ it do
270
+ tag = Tag.create!(name: 'tag', taggable_type: Item.name, taggable_id: 1)
271
+ association = tag.class.reflect_on_all_associations.first
272
+ importer.remote_local_map["items"] = {1 => 3}
273
+ importer.new_foreign_keys_for_belongs_to_polymorphic(tag, association).must_equal(["taggable_id", 3])
274
+ end
275
+ end
276
+
277
+ describe "update_foreign_keys" do
278
+ it "does nothing with no new foreign keys" do
279
+ assert_nil importer.update_foreign_keys(Item.new)
280
+ end
281
+
282
+ it "updates foreign keys" do
283
+ record.item_id = 2
284
+ importer.remote_local_map["items"] = {2 => 1}
285
+
286
+ TestClass.const_get(:ActiveRecord_Relation).any_instance.expects(:update_all).once.with({'item_id' => 1})
287
+ record.expects(:reload).returns(record)
288
+
289
+ importer.update_foreign_keys(record).must_equal([TestClass, record.id])
290
+ end
291
+
292
+ it "updates foreign keys db" do
293
+ record.item_id = 2
294
+ record.save!
295
+
296
+ importer.remote_local_map["items"] = {2 => 1}
297
+
298
+ importer.update_foreign_keys(record).must_equal([TestClass, record.id])
299
+
300
+ record.reload
301
+ assert_equal 1, record.item_id
302
+ end
303
+
304
+ it "skips default scopes" do
305
+ item_id = 2
306
+ a = 4
307
+ scoped_record = ScopedTestClass.create!(a: a, item_id: item_id)
308
+
309
+ importer.remote_local_map["items"] = {2 => 1}
310
+
311
+ importer.update_foreign_keys(scoped_record)
312
+
313
+ scoped_record.reload
314
+ assert_equal 1, scoped_record.item_id
315
+ end
316
+
317
+ it "updates polymorphic foreign keys" do
318
+ tag = Tag.create!(name: 'tag', taggable_type: Item.name, taggable_id: 2)
319
+ importer.remote_local_map["items"] = {2 => 1}
320
+
321
+ Tag.const_get(:ActiveRecord_Relation).any_instance.expects(:update_all).once.with({'taggable_id' => 1})
322
+ tag.expects(:reload).returns(tag)
323
+
324
+ importer.update_foreign_keys(tag).must_equal([Tag, tag.id])
325
+ end
326
+ end
327
+
328
+ describe "column_changes" do
329
+ describe "change_columns" do
330
+ it "changes" do
331
+ importer.column_changes = {"building_version_id" => "model_id"}
332
+ importer.change_columns({"building_version_id" => 2}).must_equal({"model_id" => 2})
333
+ end
334
+
335
+ it "doesn't affect other columns" do
336
+ importer.column_changes = {"a" => "b"}
337
+ importer.change_columns({"c" => 2}).must_equal({"c" => 2})
338
+ end
339
+ end
340
+
341
+ it "change during update" do
342
+ importer.column_changes["building_version_id"] = "item_id"
343
+ record.expects(:save!)
344
+ importer.update(TestClass.table_name, [{"building_version_id" => 2, "id" => 1}]).must_equal([[record.class, record.id]])
345
+ record.item_id.must_equal(2)
346
+ end
347
+
348
+ it "change during insert" do
349
+ importer.column_changes["building_version_id"] = "item_id"
350
+ importer.expects(:import_record).with(TestClass, {"item_id" => 2, "id" => 1}).once.returns([TestClass, record.id])
351
+ importer.import_table(TestClass.table_name, [{"item_id" => 2, "id" => 1}]).must_equal([[TestClass, record.id]])
352
+ end
353
+ end
354
+ end