cached_resource 8.0.0 → 9.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,602 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe CachedResource do
4
-
5
- def read_from_cache(key)
6
- Thing.send(:cache_read, key)
7
- end
8
-
9
- before(:each) do
10
- class Thing < ActiveResource::Base
11
- self.site = "http://api.thing.com"
12
- cached_resource
13
- end
14
-
15
- class NotTheThing < ActiveResource::Base
16
- self.site = "http://api.notthething.com"
17
- cached_resource
18
- end
19
-
20
- @thing = {:thing => {:id => 1, :name => "Ada"}}
21
- @thing_collection = [{:id => 1, :name => "Ada"}, {:id => 2, :name => "Ada", :major => 'CS'}]
22
- @thing_collection2 = [{:id => 2, :name => "Ada", :major => 'CS'}]
23
- @other_thing = {:thing => {:id => 1, :name => "Ari"}}
24
- @thing2 = {:thing => {:id => 2, :name => "Joe"}}
25
- @other_thing2 = {:thing => {:id => 2, :name => "Jeb"}}
26
- @thing3 = {:thing => {:id => 3, :name => "Stu"}}
27
- @string_thing = {:thing => {:id => "fded", :name => "Lev"}}
28
- @other_string_thing = {:thing => {:id => "fded", :name => "Lon"}}
29
- @date_thing = {:thing => {:id => 4, :created_at => DateTime.new(2020)}}
30
- @thing_json = @thing.to_json
31
- @other_thing_json = @other_thing.to_json
32
- @string_thing_json = @string_thing.to_json
33
- @other_string_thing_json = @other_string_thing.to_json
34
- @date_thing_json = @date_thing.to_json
35
- @nil_thing = nil.to_json
36
- @empty_array_thing = [].to_json
37
- @not_the_thing = {:not_the_thing => {:id => 1, :name => "Not"}}
38
- @not_the_thing_json = @not_the_thing.to_json
39
- end
40
-
41
- after(:each) do
42
- Thing.cached_resource.cache.clear
43
- Object.send(:remove_const, :Thing)
44
- NotTheThing.cached_resource.cache.clear
45
- Object.send(:remove_const, :NotTheThing)
46
- end
47
-
48
- describe "when enabled" do
49
- before(:each) do
50
- # it's on by default, but lets call the method
51
- # to make sure it works
52
- Thing.cached_resource.cache.clear
53
- Thing.cached_resource.on!
54
- NotTheThing.cached_resource.cache.clear
55
- NotTheThing.cached_resource.on!
56
-
57
- ActiveResource::HttpMock.reset!
58
- ActiveResource::HttpMock.respond_to do |mock|
59
- mock.get "/things/1.json", {}, @thing_json
60
- mock.get "/things/1.json?foo=bar", {}, @thing_json
61
- mock.get "/things/fded.json", {}, @string_thing_json
62
- mock.get "/things.json?name=42", {}, @nil_thing, 404
63
- mock.get "/things.json?name=43", {}, @empty_array_thing
64
- mock.get "/things/4.json", {}, @date_thing_json
65
- mock.get "/not_the_things/1.json", {}, @not_the_thing_json
66
- end
67
- end
68
-
69
- shared_examples "caching" do
70
- it "should cache a response" do
71
- result = Thing.find(1)
72
- read_from_cache("thing/1").should == result
73
- end
74
-
75
- it "shouldn't cache nil response" do
76
- Thing.find(:all, :params => { :name => '42' })
77
- read_from_cache("thing/all/name/42").should == nil
78
- end
79
-
80
- it "shouldn't cache blank response" do
81
- Thing.find(:all, :params => { :name => '43' })
82
- read_from_cache("thing/all/name/43").should == nil
83
- end
84
- end
85
-
86
- include_examples "caching"
87
-
88
- context 'when concurrency is turned on' do
89
- include_examples "caching"
90
- end
91
-
92
- it "should cache a response for a string primary key" do
93
- result = Thing.find("fded")
94
- read_from_cache("thing/fded").should == result
95
- end
96
-
97
- it "should cache without whitespace in keys" do
98
- result = Thing.find(1, :from => 'path', :params => { :foo => 'bar' })
99
- read_from_cache('thing/1/{:from=>"path",:params=>{:foo=>"bar"}}').should == result
100
- end
101
-
102
- it "should empty the cache when clear_cache is called" do
103
- result = Thing.find(1)
104
- Thing.clear_cache
105
- read_from_cache("thing/1").should == nil
106
- end
107
-
108
- it "should not empty the cache of NotTheThing when clear_cache is called on the Thing" do
109
- result1 = Thing.find(1)
110
- result2 = NotTheThing.find(1)
111
- Thing.clear_cache
112
- NotTheThing.send(:cache_read, 'notthething/1').should == result2
113
- end
114
-
115
- it "should empty all the cache when clear_cache is called on the Thing with :all option set" do
116
- result1 = Thing.find(1)
117
- result2 = NotTheThing.find(1)
118
- Thing.clear_cache(all: true)
119
- NotTheThing.send(:cache_read, 'notthething/1').should == nil
120
- end
121
-
122
- it "should cache a response with the same persistence" do
123
- result1 = Thing.find(1)
124
- result1.persisted?.should be true
125
- result2 = Thing.find(1)
126
- result1.persisted?.should == result2.persisted?
127
- end
128
-
129
- it "should read a response when the request is made again" do
130
- # make a request
131
- Thing.find(1)
132
- # make the same request
133
- Thing.find(1)
134
- # only one request should have happened
135
- ActiveResource::HttpMock.requests.length.should == 1
136
- end
137
-
138
- it "should read a response when the request is made again for a string primary key" do
139
- # make a request
140
- Thing.find("fded")
141
- # make the same request
142
- Thing.find("fded")
143
- # only one request should have happened
144
- ActiveResource::HttpMock.requests.length.should == 1
145
- end
146
-
147
- it "should remake a request when reloaded" do
148
- # make a request
149
- Thing.find(1)
150
- # make the same request, but reload it
151
- Thing.find(1, :reload => true)
152
- # we should get two requests
153
- ActiveResource::HttpMock.requests.length.should == 2
154
- end
155
-
156
- it "should remake a request when reloaded for a string primary key" do
157
- # make a request
158
- Thing.find("fded")
159
- # make the same request, but reload it
160
- Thing.find("fded", :reload => true)
161
- # we should get two requests
162
- ActiveResource::HttpMock.requests.length.should == 2
163
- end
164
-
165
- it "should rewrite the cache when the request is reloaded" do
166
- # make a request
167
- Thing.find(1)
168
- # get the cached result of the request
169
- old_result = read_from_cache("thing/1")
170
-
171
- # change the response
172
- ActiveResource::HttpMock.reset!
173
- ActiveResource::HttpMock.respond_to do |mock|
174
- mock.get "/things/1.json", {}, @other_thing_json
175
- end
176
-
177
- Thing.find(1, :reload => true)
178
- new_result = read_from_cache("thing/1")
179
- # since active resources are equal if and only if they
180
- # are the same object or an instance of the same class,
181
- # not new?, and have the same id.
182
- new_result.name.should_not == old_result.name
183
- end
184
-
185
- it "should remake the request when the ttl expires" do
186
- # set cache time to live to 1 second
187
- Thing.cached_resource.ttl = 1
188
- # make a request
189
- Thing.find(1)
190
- # wait for the cache to expire
191
- sleep(1.5)
192
- # make the same request
193
- Thing.find(1)
194
- ActiveResource::HttpMock.requests.length.should == 2
195
- end
196
-
197
- it "should not return a frozen object on first request" do
198
- result1 = Thing.find(1)
199
- result1.should_not be_frozen
200
- end
201
-
202
- it "should not return frozen object on a subsequent request" do
203
- result1 = Thing.find(1)
204
- result2 = Thing.find(1)
205
- result2.should_not be_frozen
206
- end
207
-
208
- it "should not freeze first requested object on a subsequent request" do
209
- result1 = Thing.find(1)
210
- result2 = Thing.find(1)
211
- result1.should_not be_frozen
212
- end
213
-
214
- describe "when ActiveSupport.parse_json_times is enabled" do
215
- before(:all) do
216
- Time.zone = 'UTC'
217
- ActiveSupport.parse_json_times = true
218
- end
219
-
220
- it "should convert date times to objects when reading from cache" do
221
- Thing.find(4)
222
-
223
- read_from_cache("thing/4").created_at.should == @date_thing[:thing][:created_at]
224
- end
225
- end
226
-
227
- shared_examples "collection_return_type" do
228
- if ActiveResource::VERSION::MAJOR >= 4
229
- it "should return an ActiveResource::Collection" do
230
- cached = read_from_cache("thing/all")
231
- cached.should be_instance_of(ActiveResource::Collection)
232
- end
233
-
234
- it "should return a chainable instance of the collection_parser" do
235
- Thing.cached_resource.cache.clear
236
- class CustomCollection < ActiveResource::Collection; end
237
- Thing.collection_parser = CustomCollection
238
-
239
- ActiveResource::HttpMock.respond_to do |mock|
240
- mock.get "/things.json?name=ada", {}, @thing_collection.to_json
241
- mock.get "/things.json?major=CS&name=ada", {}, @thing_collection2.to_json
242
- end
243
-
244
- non_cached = Thing.where(name: 'ada')
245
- non_cached.original_params.should == { :name => 'ada' }
246
- non_cached.map(&:id).should == @thing_collection.map { |h| h[:id]}
247
-
248
- cached = read_from_cache('thing/all/{:params=>{:name=>"ada"}}')
249
- cached.should be_instance_of(CustomCollection)
250
- cached.original_params.should == { :name => 'ada' }
251
- cached.resource_class.should == Thing
252
- cached.map(&:id).should == @thing_collection.map { |h| h[:id]}
253
-
254
- if ActiveResource::VERSION::MAJOR < 5
255
- non_cached = cached.resource_class.where(cached.original_params.merge(major: 'CS'))
256
- else
257
- non_cached = cached.where(major: 'CS')
258
- end
259
-
260
- non_cached.original_params.should == { :name => 'ada', :major => 'CS' }
261
- non_cached.resource_class.should == Thing
262
- non_cached.map(&:id).should == @thing_collection2.map { |h| h[:id]}
263
- cached = read_from_cache('thing/all/{:params=>{:name=>"ada",:major=>"cs"}}')
264
- cached.original_params.should == { :name => 'ada', :major => 'CS' }
265
- cached.resource_class.should == Thing
266
- cached.map(&:id).should == @thing_collection2.map { |h| h[:id]}
267
- end
268
- else
269
- it "should return an Array" do
270
- cached = read_from_cache("thing/all")
271
- cached.should be_instance_of(Array)
272
- end
273
- end
274
- end
275
-
276
- shared_examples "collection_freezing" do
277
- it "should not return a frozen collection on first request" do
278
- Thing.cached_resource.cache.clear
279
- collection1 = Thing.all
280
- collection1.should_not be_frozen
281
- end
282
-
283
- it "should not return a frozen collection on a subsequent request" do
284
- Thing.cached_resource.cache.clear
285
- collection1 = Thing.all
286
- collection2 = Thing.all
287
- collection2.should_not be_frozen
288
- end
289
-
290
- it "should not freeze first requested collection on a subsequent request" do
291
- Thing.cached_resource.cache.clear
292
- result1 = Thing.all
293
- result2 = Thing.all
294
- result1.should_not be_frozen
295
- end
296
-
297
- it "should not return frozen members on first request" do
298
- Thing.cached_resource.cache.clear
299
- collection1 = Thing.all
300
- collection1.first.should_not be_frozen
301
- end
302
-
303
- it "should not return frozen members on a subsequent request" do
304
- Thing.cached_resource.cache.clear
305
- collection1 = Thing.all
306
- collection2 = Thing.all
307
- collection2.first.should_not be_frozen
308
- end
309
-
310
- it "should not freeze members on a subsequent request" do
311
- Thing.cached_resource.cache.clear
312
- collection1 = Thing.all
313
- member1 = Thing.find(1)
314
- collection1.first.should_not be_frozen
315
- end
316
-
317
- end
318
-
319
- shared_examples "collection_cache_clearing" do
320
- it "should empty the cache when clear_cache is called" do
321
- Thing.clear_cache
322
- read_from_cache("thing/all").should == nil
323
- read_from_cache("thing/1").should == nil
324
- end
325
-
326
- end
327
-
328
- describe "when collection synchronize is enabled" do
329
- before(:each) do
330
- Thing.cached_resource.cache.clear
331
- Thing.cached_resource.collection_synchronize = true
332
-
333
- ActiveResource::HttpMock.reset!
334
- ActiveResource::HttpMock.respond_to do |mock|
335
- mock.get "/things/1.json", {}, @thing_json
336
- mock.get "/things/fded.json", {}, @string_thing_json
337
- mock.get "/things.json", {}, [@thing[:thing], @string_thing[:thing]].to_json(:root => :thing)
338
- end
339
-
340
- # make a request for all things
341
- Thing.all
342
- end
343
-
344
- include_examples "collection_return_type"
345
-
346
- include_examples "collection_freezing"
347
-
348
- it "should write cache entries for its members" do
349
- result = Thing.find(1)
350
- string_result = Thing.find("fded")
351
- # only the all request should have been made
352
- ActiveResource::HttpMock.requests.length.should == 1
353
- # the result should be cached with the appropriate key
354
- read_from_cache("thing/1").should == result
355
- read_from_cache("thing/fded").should == string_result
356
- end
357
-
358
- include_examples "collection_cache_clearing"
359
-
360
- it "should rewrite cache entries for its members when reloaded" do
361
- # get the soon to be stale result so that we have a cache entry
362
- old_result = Thing.find(1)
363
- old_string_result = Thing.find("fded")
364
- # change the server
365
- ActiveResource::HttpMock.respond_to do |mock|
366
- mock.get "/things/1.json", {}, @other_thing_json
367
- mock.get "/things/fded.json", {}, @other_string_thing_json
368
- mock.get "/things.json", {}, [@other_thing[:thing], @other_string_thing[:thing]].to_json(:root => :thing)
369
- end
370
- # reload the collection
371
- Thing.all(:reload => true)
372
- # get the updated result, read from the cache
373
- result = Thing.find(1)
374
- read_from_cache("thing/all")[0].should == result
375
- read_from_cache("thing/all")[0].name.should == result.name
376
- string_result = Thing.find("fded")
377
- read_from_cache("thing/all")[1].should == string_result
378
- read_from_cache("thing/all")[1].name.should == string_result.name
379
- end
380
-
381
- it "should update the collection when an individual request is reloaded" do
382
- # change the server
383
- ActiveResource::HttpMock.respond_to do |mock|
384
- mock.get "/things/1.json", {}, @other_thing_json
385
- mock.get "/things/fded.json", {}, @other_string_thing_json
386
- mock.get "/things.json", {}, [@other_thing[:thing], @other_string_thing[:thing]].to_json(:root => :thing)
387
- end
388
-
389
- # reload the individual
390
- result = Thing.find(1, :reload => true)
391
- read_from_cache("thing/all")[0].should == result
392
- read_from_cache("thing/all")[0].name.should == result.name
393
- string_result = Thing.find("fded", :reload => true)
394
- read_from_cache("thing/all")[1].should == string_result
395
- read_from_cache("thing/all")[1].name.should == string_result.name
396
- end
397
-
398
- it "should update both the collection and the member cache entries when a subset of the collection is retrieved" do
399
- # create cache entries for 1 and all
400
- old_individual = Thing.find(1)
401
- old_collection = Thing.all
402
-
403
- # change the server
404
- ActiveResource::HttpMock.respond_to do |mock|
405
- mock.get "/things.json?name=Ari", {}, [@other_thing[:thing]].to_json(:root => :thing)
406
- mock.get "/things.json?name=Lon", {}, [@other_string_thing[:thing]].to_json(:root => :thing)
407
- end
408
-
409
- # make a request for a subset of the "mother" collection
410
- result = Thing.find(:all, :params => {:name => "Ari"})
411
- # the collection should be updated to reflect the server change
412
- read_from_cache("thing/all")[0].should == result[0]
413
- read_from_cache("thing/all")[0].name.should == result[0].name
414
- # the individual should be updated to reflect the server change
415
- read_from_cache("thing/1").should == result[0]
416
- read_from_cache("thing/1").name.should == result[0].name
417
-
418
- # make a request for a subset of the "mother" collection
419
- result = Thing.find(:all, :params => {:name => "Lon"})
420
- # the collection should be updated to reflect the server change
421
- read_from_cache("thing/all")[1].should == result[0]
422
- read_from_cache("thing/all")[1].name.should == result[0].name
423
- # the individual should be updated to reflect the server change
424
- read_from_cache("thing/fded").should == result[0]
425
- read_from_cache("thing/fded").name.should == result[0].name
426
- end
427
-
428
- it "should maintain the order of the collection when updating it" do
429
- # change the server to return a longer collection
430
- ActiveResource::HttpMock.respond_to do |mock|
431
- mock.get "/things.json", {}, [@thing[:thing], @thing3[:thing], @thing2[:thing], @string_thing[:thing]].to_json(:root => :thing)
432
- end
433
-
434
- # create cache entry for the collection (we reload because in before block we make an all request)
435
- old_collection = Thing.all(:reload => true)
436
-
437
- # change the server's response for the thing with id 2
438
- ActiveResource::HttpMock.respond_to do |mock|
439
- mock.get "/things/2.json", {}, @other_thing2.to_json(:root => :thing)
440
- mock.get "/things/fded.json", {}, @other_string_thing.to_json(:root => :thing)
441
- end
442
-
443
- # get thing 2, thereby updating the collection
444
- result = Thing.find(2, :reload => true)
445
- # get the updated collection from the cache
446
- updated_collection = Thing.all
447
- # name should have changed to "Jeb"
448
- updated_collection[2].name.should == result.name
449
- # the updated collection should have the elements in the same order
450
- old_collection.each_with_index do |thing, i|
451
- updated_collection[i].id.should == thing.id
452
- end
453
-
454
- # get string thing, thereby updating the collection
455
- string_result = Thing.find("fded", :reload => true)
456
- # get the updated collection from the cache
457
- updated_collection = Thing.all
458
- # name should have changed to "Lon"
459
- updated_collection[3].name.should == string_result.name
460
- # the updated collection should have the elements in the same order
461
- old_collection.each_with_index do |thing, i|
462
- updated_collection[i].id.should == thing.id
463
- end
464
- end
465
- end
466
-
467
- describe "when collection synchronize is disabled" do
468
- before(:each) do
469
- Thing.cached_resource.cache.clear
470
- Thing.cached_resource.collection_synchronize = false
471
-
472
- ActiveResource::HttpMock.reset!
473
- ActiveResource::HttpMock.respond_to do |mock|
474
- mock.get "/things/1.json", {}, @thing_json
475
- mock.get "/things/fded.json", {}, @string_thing_json
476
- mock.get "/things.json", {}, [@thing[:thing], @string_thing[:thing]].to_json(:root => :thing)
477
- end
478
-
479
- # make a request for all things
480
- Thing.all
481
- end
482
-
483
- include_examples "collection_return_type"
484
-
485
- include_examples "collection_freezing"
486
-
487
- it "should not write cache entries for its members" do
488
- result = Thing.find(1)
489
- result = Thing.find("fded")
490
- # both the all in the before each and this request should have been made
491
- ActiveResource::HttpMock.requests.length.should == 3
492
- end
493
-
494
- include_examples "collection_cache_clearing"
495
-
496
- it "should not update the collection when an individual request is reloaded" do
497
- # change the server
498
- ActiveResource::HttpMock.respond_to do |mock|
499
- mock.get "/things/1.json", {}, @other_thing_json
500
- mock.get "/things/fded.json", {}, @other_string_thing_json
501
- mock.get "/things.json", {}, [@other_thing[:thing], @other_string_thing[:thing]].to_json(:root => :thing)
502
- end
503
-
504
- # reload the individual
505
- result = Thing.find(1, :reload => true)
506
- # the ids are the same, but the names should be different
507
- read_from_cache("thing/all")[0].name.should_not == result.name
508
-
509
- # reload the individual
510
- string_result = Thing.find("fded", :reload => true)
511
- # the ids are the same, but the names should be different
512
- read_from_cache("thing/all")[1].name.should_not == string_result.name
513
- end
514
- end
515
-
516
- describe "when ttl randomization is enabled" do
517
- before(:each) do
518
- @ttl = 1
519
- Thing.cached_resource.ttl = @ttl
520
- Thing.cached_resource.ttl_randomization = true
521
- Thing.cached_resource.send(:sample_range, 1..2, @ttl)
522
- # next ttl 1.72032449344216
523
- end
524
-
525
- it "should generate a random ttl" do
526
- Thing.cached_resource.cache.should_receive(:write)
527
- Thing.cached_resource.cache.stub(:write) do |key, value, options|
528
- # we know the ttl should not be the same as the set ttl
529
- options[:expires_in].should_not == @ttl
530
- end
531
-
532
- Thing.find(1)
533
- end
534
-
535
- end
536
- end
537
-
538
- describe "when disabled" do
539
- before(:each) do
540
- Thing.cached_resource.cache.clear
541
- Thing.cached_resource.off!
542
-
543
- ActiveResource::HttpMock.reset!
544
- ActiveResource::HttpMock.respond_to do |mock|
545
- mock.get "/things/1.json", {}, @thing_json
546
- mock.get "/things/fded.json", {}, @string_thing_json
547
- end
548
- end
549
-
550
- it "should not cache a response" do
551
- result = Thing.find(1)
552
- read_from_cache("thing/1").should be_nil
553
- end
554
-
555
- it "should always remake the request" do
556
- Thing.find(1)
557
- ActiveResource::HttpMock.requests.length.should == 1
558
- Thing.find(1)
559
- ActiveResource::HttpMock.requests.length.should == 2
560
- end
561
-
562
- it "should always remake the request for a string primary key" do
563
- Thing.find("fded")
564
- ActiveResource::HttpMock.requests.length.should == 1
565
- Thing.find("fded")
566
- ActiveResource::HttpMock.requests.length.should == 2
567
- end
568
- end
569
-
570
- describe "when cache_collections is disabled" do
571
- before(:each) do
572
- Thing.cached_resource.cache.clear
573
- Thing.cached_resource.cache_collections = false
574
-
575
- ActiveResource::HttpMock.reset!
576
- ActiveResource::HttpMock.respond_to do |mock|
577
- mock.get "/things.json", {}, [@thing[:thing],@string_thing[:thing]].to_json(:root => :thing)
578
- mock.get "/things/1.json", {}, @thing_json
579
- mock.get "/things/fded.json", {}, @string_thing_json
580
- end
581
- end
582
-
583
- it "should cache a response" do
584
- result = Thing.find(1)
585
- read_from_cache("thing/1").should == result
586
- end
587
-
588
- it "should not remake a single request" do
589
- result = Thing.find(1)
590
- ActiveResource::HttpMock.requests.length.should == 1
591
- result = Thing.find(1)
592
- ActiveResource::HttpMock.requests.length.should == 1
593
- end
594
-
595
- it "should always remake the request for collections" do
596
- Thing.all
597
- ActiveResource::HttpMock.requests.length.should == 1
598
- Thing.all
599
- ActiveResource::HttpMock.requests.length.should == 2
600
- end
601
- end
602
- end