remotable 0.3.0 → 0.4.0.beta2

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.
@@ -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