remotable 0.3.0 → 0.4.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,15 +1,15 @@
1
1
  module Remotable
2
2
  module ValidateModels
3
-
4
-
3
+
4
+
5
5
  def validate_models=(val)
6
6
  @validate_models = (val == true)
7
7
  end
8
-
8
+
9
9
  def validate_models?
10
10
  @validate_models == true
11
11
  end
12
-
12
+
13
13
  def without_validation
14
14
  value = self.validate_models?
15
15
  self.validate_models = false
@@ -17,7 +17,7 @@ module Remotable
17
17
  ensure
18
18
  self.validate_models = value
19
19
  end
20
-
21
-
20
+
21
+
22
22
  end
23
23
  end
@@ -1,3 +1,3 @@
1
1
  module Remotable
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0.beta2"
3
3
  end
@@ -1,18 +1,18 @@
1
1
  module Remotable
2
2
  class WithRemoteModelProxy
3
-
3
+
4
4
  def initialize(model, remote_model)
5
5
  @model = model
6
6
  @remote_model = remote_model
7
7
  end
8
-
8
+
9
9
  delegate :respond_to?, :to => :@model
10
-
10
+
11
11
  def method_missing(sym, *args, &block)
12
12
  @model.with_remote_model(@remote_model) do
13
13
  @model.send(sym, *args, &block)
14
14
  end
15
15
  end
16
-
16
+
17
17
  end
18
18
  end
@@ -1,30 +1,31 @@
1
1
  require "test_helper"
2
2
  require "remotable"
3
3
  require "support/active_resource"
4
+ require "support/concurrently"
4
5
  require "active_resource_simulator"
5
6
  require "rr"
6
7
 
7
8
 
8
9
  class ActiveResourceTest < ActiveSupport::TestCase
9
10
  include RR::Adapters::TestUnit
10
-
11
+
11
12
  test "should make an absolute path and add the format" do
12
13
  assert_equal "/api/accounts/by_slug/value.json", RemoteTenant.expanded_path_for("by_slug/value")
13
14
  end
14
-
15
-
16
-
17
-
15
+
16
+
17
+
18
+
18
19
  # ========================================================================= #
19
20
  # Finding #
20
21
  # ========================================================================= #
21
-
22
+
22
23
  test "should be able to find resources by different attributes" do
23
24
  new_tenant_slug = "not_found"
24
-
25
+
25
26
  assert_equal 0, Tenant.where(:slug => new_tenant_slug).count,
26
27
  "There's not supposed to be a Tenant with the slug #{new_tenant_slug}."
27
-
28
+
28
29
  assert_difference "Tenant.count", +1 do
29
30
  RemoteTenant.run_simulation do |s|
30
31
  s.show(nil, {
@@ -32,20 +33,20 @@ class ActiveResourceTest < ActiveSupport::TestCase
32
33
  :slug => new_tenant_slug,
33
34
  :church_name => "Not Found"
34
35
  }, :path => "/api/accounts/by_slug/#{new_tenant_slug}.json")
35
-
36
+
36
37
  new_tenant = Tenant.find_by_slug(new_tenant_slug)
37
38
  assert_not_nil new_tenant, "A remote tenant was not found with the slug #{new_tenant_slug.inspect}"
38
39
  end
39
40
  end
40
41
  end
41
-
42
+
42
43
  test "should be able to find resources with a composite key" do
43
44
  group_id = 5
44
45
  slug = "not_found"
45
-
46
+
46
47
  assert_equal 0, RemoteWithCompositeKey.where(:group_id => group_id, :slug => slug).count,
47
48
  "There's not supposed to be a Tenant with the group_id #{group_id} and the slug #{slug}."
48
-
49
+
49
50
  assert_difference "RemoteWithCompositeKey.count", +1 do
50
51
  RemoteTenant.run_simulation do |s|
51
52
  s.show(nil, {
@@ -54,19 +55,19 @@ class ActiveResourceTest < ActiveSupport::TestCase
54
55
  :slug => slug,
55
56
  :church_name => "Not Found"
56
57
  }, :path => "/api/accounts/groups/#{group_id}/tenants/#{slug}.json")
57
-
58
+
58
59
  new_tenant = RemoteWithCompositeKey.find_by_group_id_and_slug(group_id, slug)
59
60
  assert_not_nil new_tenant, "A remote tenant was not found with the group_id #{group_id} and the slug #{slug}."
60
61
  end
61
62
  end
62
63
  end
63
-
64
+
64
65
  test "should be able to find resources with the bang method" do
65
66
  new_tenant_slug = "not_found2"
66
-
67
+
67
68
  assert_equal 0, Tenant.where(:slug => new_tenant_slug).count,
68
69
  "There's not supposed to be a Tenant with the slug #{new_tenant_slug}."
69
-
70
+
70
71
  assert_difference "Tenant.count", +1 do
71
72
  RemoteTenant.run_simulation do |s|
72
73
  s.show(nil, {
@@ -74,34 +75,34 @@ class ActiveResourceTest < ActiveSupport::TestCase
74
75
  :slug => new_tenant_slug,
75
76
  :church_name => "Not Found"
76
77
  }, :path => "/api/accounts/by_slug/#{new_tenant_slug}.json")
77
-
78
+
78
79
  new_tenant = Tenant.find_by_slug!(new_tenant_slug)
79
80
  assert_not_nil new_tenant, "A remote tenant was not found with the slug #{new_tenant_slug.inspect}"
80
81
  end
81
82
  end
82
83
  end
83
-
84
+
84
85
  test "if a resource is neither local nor remote, raise an exception with the bang method" do
85
86
  new_tenant_slug = "not_found3"
86
-
87
+
87
88
  assert_equal 0, Tenant.where(:slug => new_tenant_slug).count,
88
89
  "There's not supposed to be a Tenant with the slug #{new_tenant_slug}."
89
-
90
+
90
91
  RemoteTenant.run_simulation do |s|
91
92
  s.show(nil, nil, :status => 404, :path => "/api/accounts/by_slug/#{new_tenant_slug}.json")
92
-
93
+
93
94
  assert_raises ActiveRecord::RecordNotFound do
94
95
  Tenant.find_by_slug!(new_tenant_slug)
95
96
  end
96
97
  end
97
98
  end
98
-
99
+
99
100
  test "should be able to find resources by different attributes and specify a path" do
100
101
  new_tenant_name = "JohnnyG"
101
-
102
+
102
103
  assert_equal 0, Tenant.where(:name => new_tenant_name).count,
103
104
  "There's not supposed to be a Tenant with the name #{new_tenant_name}."
104
-
105
+
105
106
  assert_difference "Tenant.count", +1 do
106
107
  RemoteTenant.run_simulation do |s|
107
108
  s.show(nil, {
@@ -109,135 +110,164 @@ class ActiveResourceTest < ActiveSupport::TestCase
109
110
  :slug => "not_found",
110
111
  :church_name => new_tenant_name
111
112
  }, :path => "/api/accounts/by_nombre/#{new_tenant_name}.json")
112
-
113
+
113
114
  new_tenant = Tenant.find_by_name(new_tenant_name)
114
115
  assert_not_nil new_tenant, "A remote tenant was not found with the name #{new_tenant_name.inspect}"
115
116
  end
116
117
  end
117
118
  end
118
-
119
-
120
-
121
-
119
+
120
+ test "should not try to create a local record twice when 2 or more threads are fetching a new remote resource concurrently" do
121
+ slug = "unique"
122
+
123
+ stub(RemoteWithKey).fetch_by(:slug, slug) do
124
+ sleep 0.01
125
+ RemoteWithKey.nosync { RemoteWithKey.create!(slug: slug) }
126
+ end
127
+
128
+ afterwards do
129
+ RemoteWithKey.where(slug: slug).delete_all
130
+ end
131
+
132
+ refute_raises ActiveRecord::RecordNotUnique do
133
+ concurrently do
134
+ RemoteWithKey.find_by_slug(slug)
135
+ end
136
+ end
137
+ end
138
+
139
+ test "should URI escape remote attributes" do
140
+ bad_uri = "http://() { :; }; ping -c 23 0.0.0.0"
141
+ route = "groups/:group_id/tenants/:slug"
142
+ simple_key_path = Tenant.remote_path_for_simple_key("tenants/:slug", :slug, bad_uri)
143
+ composite_key_path = Tenant.remote_path_for_composite_key(route, [:group_id, :slug], [5, bad_uri])
144
+
145
+ assert_equal "tenants/http://()%20%7B%20:;%20%7D;%20ping%20-c%2023%200.0.0.0", simple_key_path
146
+ assert_equal "groups/5/tenants/http://()%20%7B%20:;%20%7D;%20ping%20-c%2023%200.0.0.0", composite_key_path
147
+ end
148
+
149
+
150
+
151
+
122
152
  # ========================================================================= #
123
153
  # Expiration #
124
154
  # ========================================================================= #
125
-
155
+
126
156
  test "should not fetch a remote record when a local record is not expired" do
127
157
  tenant = Factory(:tenant, :expires_at => 100.years.from_now)
128
158
  unexpected_name = "Totally Wonky"
129
-
159
+
130
160
  RemoteTenant.run_simulation do |s|
131
161
  s.show(tenant.remote_id, {
132
162
  :id => tenant.remote_id,
133
163
  :slug => tenant.slug,
134
164
  :church_name => unexpected_name
135
165
  })
136
-
166
+
137
167
  tenant = Tenant.find_by_remote_id(tenant.remote_id)
138
168
  assert_not_equal unexpected_name, tenant.name
139
169
  end
140
170
  end
141
-
171
+
142
172
  test "should fetch a remote record when a local record is expired" do
143
173
  tenant = Factory(:tenant, :expires_at => 1.year.ago)
144
174
  unexpected_name = "Totally Wonky"
145
-
175
+
146
176
  RemoteTenant.run_simulation do |s|
147
177
  s.show(tenant.remote_id, {
148
178
  :id => tenant.remote_id,
149
179
  :slug => tenant.slug,
150
180
  :church_name => unexpected_name
151
181
  }, :headers => if_modified_since(tenant))
152
-
182
+
153
183
  tenant = Tenant.find_by_remote_id(tenant.remote_id)
154
184
  assert_equal unexpected_name, tenant.name
155
185
  end
156
186
  end
157
-
187
+
158
188
  test "should treat a 304 response as no changes" do
159
189
  tenant = Factory(:tenant, :expires_at => 1.year.ago)
160
-
190
+
161
191
  RemoteTenant.run_simulation do |s|
162
192
  s.show(tenant.remote_id, nil, :status => 304, :headers => if_modified_since(tenant))
163
-
193
+
164
194
  tenant = Tenant.find_by_remote_id(tenant.remote_id)
165
195
  assert tenant.expires_at > Time.now, "Tenant should be considered fresh"
166
196
  assert_not_nil tenant.remote_id, "The Remote Tenant's id should not be considered nil just because there was no body in the remote response"
167
197
  end
168
198
  end
169
-
199
+
170
200
  test "should ignore a 503 response" do
171
201
  expired_at = 1.year.ago
172
202
  tenant = Factory(:tenant, :expires_at => expired_at)
173
-
203
+
174
204
  RemoteTenant.run_simulation do |s|
175
205
  s.show(tenant.remote_id, nil, :status => 503, :headers => if_modified_since(tenant))
176
-
206
+
177
207
  assert_nothing_raised do
178
208
  tenant = Tenant.find_by_remote_id(tenant.remote_id)
179
209
  end
180
210
  assert_equal expired_at, tenant.expires_at, "Tenant's expiration date should not have changed"
181
211
  end
182
212
  end
183
-
184
-
185
-
186
-
213
+
214
+
215
+
216
+
187
217
  # ========================================================================= #
188
218
  # Updating #
189
219
  # ========================================================================= #
190
-
220
+
191
221
  test "should update a record remotely when updating one locally" do
192
222
  tenant = Factory(:tenant)
193
223
  new_name = "Totally Wonky"
194
-
224
+
195
225
  RemoteTenant.run_simulation do |s|
196
226
  s.show(tenant.remote_id, {
197
227
  :id => tenant.remote_id,
198
228
  :slug => "totally-wonky",
199
229
  :church_name => tenant.name
200
230
  }, :headers => if_modified_since(tenant))
201
-
231
+
202
232
  tenant.nosync = false
203
233
  tenant.name = "Totally Wonky"
204
234
  assert_equal true, tenant.any_remote_changes?
205
-
235
+
206
236
  # Throws an error if save is not called on the remote resource
207
237
  mock(tenant.remote_resource).save { true }
208
-
238
+
209
239
  tenant.save!
210
240
  assert_equal "totally-wonky", tenant.slug, "After updating a record, remote data should be merge"
211
241
  end
212
242
  end
213
-
243
+
214
244
  test "should be able to update resources by different attributes" do
215
245
  tenant = RemoteWithKey.where(id: Factory(:tenant).id).first
216
246
  new_name = "Totally Wonky"
217
-
247
+
218
248
  RemoteTenant.run_simulation do |s|
219
249
  s.show(nil, {
220
250
  :id => tenant.id,
221
251
  :slug => tenant.slug,
222
252
  :church_name => tenant.name
223
253
  }, :path => "/api/accounts/by_slug/#{tenant.slug}.json", :headers => if_modified_since(tenant))
224
-
254
+
225
255
  s.update(nil, :path => "/api/accounts/by_slug/#{tenant.slug}.json")
226
-
256
+
227
257
  tenant.nosync = false
228
258
  tenant.name = new_name
229
259
  assert_equal true, tenant.any_remote_changes?
230
-
260
+
231
261
  tenant.save!
232
-
262
+
233
263
  # pending "Not sure how to test that an update happened"
234
264
  end
235
265
  end
236
-
266
+
237
267
  test "should fail to update a record locally when failing to update one remotely" do
238
268
  tenant = Factory(:tenant)
239
269
  new_name = "Totally Wonky"
240
-
270
+
241
271
  RemoteTenant.run_simulation do |s|
242
272
  s.show(tenant.remote_id, {
243
273
  :id => tenant.remote_id,
@@ -247,7 +277,7 @@ class ActiveResourceTest < ActiveSupport::TestCase
247
277
  s.update(tenant.remote_id, :status => 422, :body => {
248
278
  :errors => {:church_name => ["is already taken"]}
249
279
  })
250
-
280
+
251
281
  tenant.nosync = false
252
282
  tenant.name = new_name
253
283
  assert_raises(ActiveRecord::RecordInvalid) do
@@ -256,61 +286,61 @@ class ActiveResourceTest < ActiveSupport::TestCase
256
286
  assert_equal ["is already taken"], tenant.errors[:name]
257
287
  end
258
288
  end
259
-
260
-
261
-
262
-
289
+
290
+
291
+
292
+
263
293
  # ========================================================================= #
264
294
  # Creating #
265
295
  # ========================================================================= #
266
-
296
+
267
297
  test "should create a record remotely when creating one locally" do
268
298
  tenant = Tenant.new({
269
299
  :slug => "brand_new",
270
300
  :name => "Brand New"
271
301
  })
272
-
302
+
273
303
  RemoteTenant.run_simulation do |s|
274
304
  s.create({
275
305
  :id => 143,
276
306
  :slug => tenant.slug,
277
307
  :church_name => tenant.name
278
308
  })
279
-
309
+
280
310
  tenant.save!
281
-
311
+
282
312
  assert_equal true, tenant.remote_resource.persisted?
283
313
  assert_equal 143, tenant.remote_id, "After creating a record, remote data should be merge"
284
314
  end
285
315
  end
286
-
316
+
287
317
  test "should fail to create a record locally when failing to create one remotely" do
288
318
  tenant = Tenant.new({
289
319
  :slug => "brand_new",
290
320
  :name => "Brand New"
291
321
  })
292
-
322
+
293
323
  RemoteTenant.run_simulation do |s|
294
324
  s.create({
295
325
  :errors => {
296
326
  :what => ["ever"],
297
327
  :church_name => ["is already taken"]}
298
328
  }, :status => 422)
299
-
329
+
300
330
  assert_raises(ActiveRecord::RecordInvalid) do
301
331
  tenant.save!
302
332
  end
303
-
333
+
304
334
  assert_equal ["is already taken"], tenant.errors[:name]
305
335
  end
306
336
  end
307
-
337
+
308
338
  test "should create a record locally when fetching a new remote resource" do
309
339
  new_tenant_id = 17
310
-
340
+
311
341
  assert_equal 0, Tenant.where(:remote_id => new_tenant_id).count,
312
342
  "There's not supposed to be a Tenant with the id #{new_tenant_id}."
313
-
343
+
314
344
  assert_difference "Tenant.count", +1 do
315
345
  RemoteTenant.run_simulation do |s|
316
346
  s.show(new_tenant_id, {
@@ -318,103 +348,121 @@ class ActiveResourceTest < ActiveSupport::TestCase
318
348
  :slug => "not_found",
319
349
  :church_name => "Not Found"
320
350
  })
321
-
351
+
322
352
  new_tenant = Tenant.find_by_remote_id(new_tenant_id)
323
353
  assert_not_nil new_tenant, "A remote tenant was not found with the id #{new_tenant_id.inspect}"
324
354
  end
325
355
  end
326
356
  end
327
-
328
-
329
-
330
-
357
+
358
+
359
+
360
+
331
361
  # ========================================================================= #
332
362
  # Destroying #
333
363
  # ========================================================================= #
334
-
364
+
335
365
  test "should destroy a record remotely when destroying one locally" do
336
366
  tenant = Factory(:tenant)
337
-
367
+
338
368
  RemoteTenant.run_simulation do |s|
339
369
  s.show(tenant.remote_id, {
340
370
  :id => tenant.remote_id,
341
371
  :slug => tenant.slug,
342
372
  :church_name => tenant.name
343
373
  }, :headers => if_modified_since(tenant))
344
-
374
+
345
375
  # Throws an error if save is not called on the remote resource
346
376
  mock(tenant.remote_resource).destroy { true }
347
-
377
+
348
378
  tenant.nosync = false
349
379
  tenant.destroy
350
380
  end
351
381
  end
352
-
353
-
382
+
354
383
  test "should destroy resources by different attributes" do
355
384
  tenant = RemoteWithKey.where(id: Factory(:tenant).id).first
356
385
  new_name = "Totally Wonky"
357
-
386
+
358
387
  RemoteTenant.run_simulation do |s|
359
388
  s.show(nil, {
360
389
  :id => tenant.id,
361
390
  :slug => tenant.slug,
362
391
  :church_name => tenant.name
363
392
  }, :path => "/api/accounts/by_slug/#{tenant.slug}.json", :headers => if_modified_since(tenant))
364
-
393
+
365
394
  s.destroy(nil, :path => "/api/accounts/by_slug/#{tenant.slug}.json")
366
-
395
+
367
396
  tenant.nosync = false
368
397
  tenant.destroy
369
398
  end
370
399
  end
371
-
400
+
372
401
  test "should fail to destroy a record locally when failing to destroy one remotely" do
373
402
  tenant = Factory(:tenant)
374
-
403
+
375
404
  RemoteTenant.run_simulation do |s|
376
405
  s.show(tenant.remote_id, {
377
406
  :id => tenant.remote_id,
378
407
  :slug => tenant.slug,
379
408
  :church_name => tenant.name
380
409
  }, :headers => if_modified_since(tenant))
381
-
382
- s.destroy(tenant.remote_id,
410
+
411
+ s.destroy(tenant.remote_id,
383
412
  :body => { :errors => { :base => ["nope"] } },
384
413
  :status => 422)
385
-
414
+
386
415
  tenant.nosync = false
387
416
  tenant.destroy
388
417
  assert_equal false, tenant.destroyed?
389
418
  assert_equal ["nope"], tenant.errors[:base]
390
419
  end
391
420
  end
392
-
421
+
422
+ test "should succeed in destroying a record locally when the remote source is not found" do
423
+ tenant = Factory(:tenant)
424
+
425
+ RemoteTenant.run_simulation do |s|
426
+ s.show(tenant.remote_id, {
427
+ :id => tenant.remote_id,
428
+ :slug => tenant.slug,
429
+ :church_name => tenant.name
430
+ }, :headers => if_modified_since(tenant))
431
+
432
+ s.destroy(tenant.remote_id,
433
+ :status => 404)
434
+
435
+ tenant.nosync = false
436
+ tenant.destroy
437
+ assert_equal true, tenant.destroyed?
438
+ end
439
+ end
440
+
393
441
  test "should delete a local record when a remote record has been deleted" do
394
442
  tenant = Factory(:tenant, :expires_at => 1.year.ago)
395
-
443
+
396
444
  assert_difference "Tenant.count", -1 do
397
445
  RemoteTenant.run_simulation do |s|
398
446
  s.show(tenant.remote_id, nil, :status => 404, :headers => if_modified_since(tenant))
399
-
447
+
400
448
  tenant = Tenant.where(:remote_id => tenant.remote_id).first
401
- assert_equal nil, tenant
449
+ assert tenant.destroyed?
402
450
  end
403
451
  end
404
452
  end
405
-
406
-
407
-
408
-
453
+
454
+
455
+
456
+
409
457
  # ========================================================================= #
410
458
  # Listing #
411
459
  # ========================================================================= #
412
-
460
+
413
461
  test "should be able to find all remote resources and sync them with local resources" do
414
462
  tenant = Factory(:tenant, :expires_at => 1.year.ago)
415
-
463
+
416
464
  assert_equal 1, Tenant.count, "There's supposed to be 1 tenant"
417
-
465
+
418
466
  # Creates 1 missing resources, updates 1
419
467
  assert_difference "Tenant.count", +1 do
420
468
  RemoteTenant.run_simulation do |s|
@@ -426,56 +474,58 @@ class ActiveResourceTest < ActiveSupport::TestCase
426
474
  :slug => "generic-slug",
427
475
  :church_name => "Generic Name" }],
428
476
  :path => "/api/accounts.json")
429
-
477
+
430
478
  tenants = Tenant.all_by_remote
431
479
  assert_equal 2, tenants.length
432
-
480
+
433
481
  assert_equal "a-different-slug", tenant.reload.slug
434
482
  end
435
483
  end
436
484
  end
437
-
438
-
439
-
440
-
485
+
486
+
487
+
488
+
441
489
  # ========================================================================= #
442
490
  # Timeouts #
443
491
  # ========================================================================= #
444
-
492
+
445
493
  test "should raise a Remotable::TimeoutError when a timeout occurs" do
446
494
  assert_raise Remotable::TimeoutError do
447
495
  stub(Tenant.remote_model).find do |*args|
448
496
  raise ActiveResource::TimeoutError, "it timed out"
449
497
  end
450
-
498
+
451
499
  Tenant.find_by_remote_id(15)
452
500
  end
453
501
  end
454
-
502
+
455
503
  test "should ignore a Remotable::TimeoutError when instantiating a record" do
456
504
  tenant = Factory(:tenant, :expires_at => 1.year.ago)
457
-
505
+
458
506
  assert_nothing_raised do
459
507
  stub(Tenant.remote_model).find do |*args|
460
508
  raise ActiveResource::TimeoutError, "it timed out"
461
509
  end
462
-
510
+
463
511
  tenant = Tenant.find_by_remote_id(tenant.remote_id)
464
512
  assert_not_nil tenant
465
513
  end
466
514
  end
467
-
468
-
469
-
470
-
515
+
516
+
517
+
518
+
471
519
  private
472
-
473
-
474
-
520
+
475
521
  def if_modified_since(record)
476
- {"If-Modified-Since" => Remotable.http_format_time(record.updated_at)}
522
+ {"If-Modified-Since" => Remotable.http_format_time(record.remote_updated_at)}
523
+ end
524
+
525
+ def refute_raises(exception)
526
+ yield
527
+ rescue exception
528
+ flunk "#{$!.class} was raised\n#{$!.message}\n#{$!.backtrace.join("\n")}"
477
529
  end
478
-
479
-
480
-
530
+
481
531
  end