rack-cache 1.2 → 1.3.0

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,268 +0,0 @@
1
- # coding: utf-8
2
- require "#{File.dirname(__FILE__)}/spec_setup"
3
- require 'rack/cache/entitystore'
4
-
5
- class Object
6
- def sha_like?
7
- length == 40 && self =~ /^[0-9a-z]+$/
8
- end
9
- end
10
-
11
- shared 'A Rack::Cache::EntityStore Implementation' do
12
- it 'responds to all required messages' do
13
- %w[read open write exist?].each do |message|
14
- @store.should.respond_to message
15
- end
16
- end
17
-
18
- it 'stores bodies with #write' do
19
- key, size = @store.write(['My wild love went riding,'])
20
- key.should.not.be.nil
21
- key.should.be.sha_like
22
-
23
- data = @store.read(key)
24
- data.should.equal 'My wild love went riding,'
25
- end
26
-
27
- it 'takes a ttl parameter for #write' do
28
- key, size = @store.write(['My wild love went riding,'], 0)
29
- key.should.not.be.nil
30
- key.should.be.sha_like
31
-
32
- data = @store.read(key)
33
- data.should.equal 'My wild love went riding,'
34
- end
35
-
36
- it 'correctly determines whether cached body exists for key with #exist?' do
37
- key, size = @store.write(['She rode to the devil,'])
38
- @store.should.exist key
39
- @store.should.not.exist '938jasddj83jasdh4438021ksdfjsdfjsdsf'
40
- end
41
-
42
- it 'can read data written with #write' do
43
- key, size = @store.write(['And asked him to pay.'])
44
- data = @store.read(key)
45
- data.should.equal 'And asked him to pay.'
46
- end
47
-
48
- it 'gives a 40 character SHA1 hex digest from #write' do
49
- key, size = @store.write(['she rode to the sea;'])
50
- key.should.not.be.nil
51
- key.length.should.equal 40
52
- key.should.be =~ /^[0-9a-z]+$/
53
- key.should.equal '90a4c84d51a277f3dafc34693ca264531b9f51b6'
54
- end
55
-
56
- it 'returns the entire body as a String from #read' do
57
- key, size = @store.write(['She gathered together'])
58
- @store.read(key).should.equal 'She gathered together'
59
- end
60
-
61
- it 'returns nil from #read when key does not exist' do
62
- @store.read('87fe0a1ae82a518592f6b12b0183e950b4541c62').should.be.nil
63
- end
64
-
65
- it 'returns a Rack compatible body from #open' do
66
- key, size = @store.write(['Some shells for her hair.'])
67
- body = @store.open(key)
68
- body.should.respond_to :each
69
- buf = ''
70
- body.each { |part| buf << part }
71
- buf.should.equal 'Some shells for her hair.'
72
- end
73
-
74
- it 'returns nil from #open when key does not exist' do
75
- @store.open('87fe0a1ae82a518592f6b12b0183e950b4541c62').should.be.nil
76
- end
77
-
78
- it 'can store largish bodies with binary data' do
79
- pony = File.open(File.dirname(__FILE__) + '/pony.jpg', 'rb') { |f| f.read }
80
- key, size = @store.write([pony])
81
- key.should.equal 'd0f30d8659b4d268c5c64385d9790024c2d78deb'
82
- data = @store.read(key)
83
- data.length.should.equal pony.length
84
- data.hash.should.equal pony.hash
85
- end
86
-
87
- it 'deletes stored entries with #purge' do
88
- key, size = @store.write(['My wild love went riding,'])
89
- @store.purge(key).should.be.nil
90
- @store.read(key).should.be.nil
91
- end
92
- end
93
-
94
- describe 'Rack::Cache::EntityStore' do
95
-
96
- describe 'Heap' do
97
- before { @store = Rack::Cache::EntityStore::Heap.new }
98
- behaves_like 'A Rack::Cache::EntityStore Implementation'
99
- it 'takes a Hash to ::new' do
100
- @store = Rack::Cache::EntityStore::Heap.new('foo' => ['bar'])
101
- @store.read('foo').should.equal 'bar'
102
- end
103
- it 'uses its own Hash with no args to ::new' do
104
- @store.read('foo').should.be.nil
105
- end
106
- end
107
-
108
- describe 'Disk' do
109
- before do
110
- @temp_dir = create_temp_directory
111
- @store = Rack::Cache::EntityStore::Disk.new(@temp_dir)
112
- end
113
- after do
114
- @store = nil
115
- remove_entry_secure @temp_dir
116
- end
117
- behaves_like 'A Rack::Cache::EntityStore Implementation'
118
-
119
- it 'takes a path to ::new and creates the directory' do
120
- path = @temp_dir + '/foo'
121
- @store = Rack::Cache::EntityStore::Disk.new(path)
122
- File.should.be.a.directory path
123
- end
124
- it 'produces a body that responds to #to_path' do
125
- key, size = @store.write(['Some shells for her hair.'])
126
- body = @store.open(key)
127
- body.should.respond_to :to_path
128
- path = "#{@temp_dir}/#{key[0..1]}/#{key[2..-1]}"
129
- body.to_path.should.equal path
130
- end
131
- it 'spreads data over a 36² hash radius' do
132
- (<<-PROSE).each_line { |line| @store.write([line]).first.should.be.sha_like }
133
- My wild love went riding,
134
- She rode all the day;
135
- She rode to the devil,
136
- And asked him to pay.
137
-
138
- The devil was wiser
139
- It's time to repent;
140
- He asked her to give back
141
- The money she spent
142
-
143
- My wild love went riding,
144
- She rode to sea;
145
- She gathered together
146
- Some shells for her hair
147
-
148
- She rode on to Christmas,
149
- She rode to the farm;
150
- She rode to Japan
151
- And re-entered a town
152
-
153
- My wild love is crazy
154
- She screams like a bird;
155
- She moans like a cat
156
- When she wants to be heard
157
-
158
- She rode and she rode on
159
- She rode for a while,
160
- Then stopped for an evening
161
- And laid her head down
162
-
163
- By this time the weather
164
- Had changed one degree,
165
- She asked for the people
166
- To let her go free
167
-
168
- My wild love went riding,
169
- She rode for an hour;
170
- She rode and she rested,
171
- And then she rode on
172
- My wild love went riding,
173
- PROSE
174
- subdirs = Dir["#{@temp_dir}/*"]
175
- subdirs.each do |subdir|
176
- File.basename(subdir).should.be =~ /^[0-9a-z]{2}$/
177
- files = Dir["#{subdir}/*"]
178
- files.each do |filename|
179
- File.basename(filename).should.be =~ /^[0-9a-z]{38}$/
180
- end
181
- files.length.should.be > 0
182
- end
183
- subdirs.length.should.equal 28
184
- end
185
- end
186
-
187
- need_memcached 'entity store tests' do
188
- describe 'MemCached' do
189
- before do
190
- @store = Rack::Cache::EntityStore::MemCached.new($memcached)
191
- end
192
- after do
193
- @store = nil
194
- end
195
- behaves_like 'A Rack::Cache::EntityStore Implementation'
196
- end
197
-
198
- describe 'options parsing' do
199
- before do
200
- uri = URI.parse("memcached://#{ENV['MEMCACHED']}/obj_ns1?show_backtraces=true")
201
- @memcached_metastore = Rack::Cache::MetaStore::MemCached.resolve uri
202
- end
203
-
204
- it 'passes options from uri' do
205
- @memcached_metastore.cache.instance_variable_get(:@options)[:show_backtraces].should.equal true
206
- end
207
-
208
- it 'takes namespace into account' do
209
- @memcached_metastore.cache.instance_variable_get(:@options)[:prefix_key].should.equal 'obj_ns1'
210
- end
211
- end
212
- end
213
-
214
-
215
- need_dalli 'entity store tests' do
216
- describe 'Dalli' do
217
- before do
218
- $dalli.flush_all
219
- @store = Rack::Cache::EntityStore::Dalli.new($dalli)
220
- end
221
- after do
222
- @store = nil
223
- end
224
- behaves_like 'A Rack::Cache::EntityStore Implementation'
225
- end
226
-
227
- describe 'options parsing' do
228
- before do
229
- uri = URI.parse("memcached://#{ENV['MEMCACHED']}/obj_ns1?show_backtraces=true")
230
- @dalli_metastore = Rack::Cache::MetaStore::Dalli.resolve uri
231
- end
232
-
233
- it 'passes options from uri' do
234
- @dalli_metastore.cache.instance_variable_get(:@options)[:show_backtraces].should.equal true
235
- end
236
-
237
- it 'takes namespace into account' do
238
- @dalli_metastore.cache.instance_variable_get(:@options)[:namespace].should.equal 'obj_ns1'
239
- end
240
- end
241
- end
242
-
243
- need_java 'entity store testing' do
244
- module Rack::Cache::AppEngine
245
- module MC
246
- class << (Service = {})
247
- def contains(key); include?(key); end
248
- def get(key); self[key]; end;
249
- def put(key, value, ttl = nil)
250
- self[key] = value
251
- end
252
- end
253
-
254
- end
255
- end
256
-
257
- describe 'GAEStore' do
258
- before do
259
- puts Rack::Cache::AppEngine::MC::Service.inspect
260
- @store = Rack::Cache::EntityStore::GAEStore.new
261
- end
262
- after do
263
- @store = nil
264
- end
265
- behaves_like 'A Rack::Cache::EntityStore Implementation'
266
- end
267
- end
268
- end
data/test/key_test.rb DELETED
@@ -1,50 +0,0 @@
1
- require "#{File.dirname(__FILE__)}/spec_setup"
2
- require 'rack/cache/key'
3
-
4
- describe 'A Rack::Cache::Key' do
5
- # Helper Methods =============================================================
6
-
7
- def mock_request(*args)
8
- uri, opts = args
9
- env = Rack::MockRequest.env_for(uri, opts || {})
10
- Rack::Cache::Request.new(env)
11
- end
12
-
13
- def new_key(request)
14
- Rack::Cache::Key.call(request)
15
- end
16
-
17
- it "sorts params" do
18
- request = mock_request('/test?z=last&a=first')
19
- new_key(request).should.include('a=first&z=last')
20
- end
21
-
22
- it "includes the scheme" do
23
- request = mock_request(
24
- '/test',
25
- 'rack.url_scheme' => 'https',
26
- 'HTTP_HOST' => 'www2.example.org'
27
- )
28
- new_key(request).should.include('https://')
29
- end
30
-
31
- it "includes host" do
32
- request = mock_request('/test', "HTTP_HOST" => 'www2.example.org')
33
- new_key(request).should.include('www2.example.org')
34
- end
35
-
36
- it "includes path" do
37
- request = mock_request('/test')
38
- new_key(request).should.include('/test')
39
- end
40
-
41
- it "sorts the query string by key/value after decoding" do
42
- request = mock_request('/test?x=q&a=b&%78=c')
43
- new_key(request).should.match(/\?a=b&x=c&x=q$/)
44
- end
45
-
46
- it "is in order of scheme, host, path, params" do
47
- request = mock_request('/test?x=y', "HTTP_HOST" => 'www2.example.org')
48
- new_key(request).should.equal "http://www2.example.org/test?x=y"
49
- end
50
- end
@@ -1,338 +0,0 @@
1
- require "#{File.dirname(__FILE__)}/spec_setup"
2
- require 'rack/cache/metastore'
3
- require 'rack/cache/entitystore'
4
-
5
- shared 'A Rack::Cache::MetaStore Implementation' do
6
-
7
- ###
8
- # Helpers
9
-
10
- def mock_request(uri, opts)
11
- env = Rack::MockRequest.env_for(uri, opts || {})
12
- Rack::Cache::Request.new(env)
13
- end
14
-
15
- def mock_response(status, headers, body)
16
- headers ||= {}
17
- body = Array(body).compact
18
- Rack::Cache::Response.new(status, headers, body)
19
- end
20
-
21
- def slurp(body)
22
- buf = ''
23
- body.each { |part| buf << part }
24
- buf
25
- end
26
-
27
- # Stores an entry for the given request args, returns a url encoded cache key
28
- # for the request.
29
- def store_simple_entry(*request_args)
30
- path, headers = request_args
31
- @request = mock_request(path || '/test', headers || {})
32
- @response = mock_response(200, {'Cache-Control' => 'max-age=420'}, ['test'])
33
- body = @response.body
34
- cache_key = @store.store(@request, @response, @entity_store)
35
- @response.body.should.not.be.same_as body
36
- cache_key
37
- end
38
-
39
- before do
40
- @request = mock_request('/', {})
41
- @response = mock_response(200, {}, ['hello world'])
42
- end
43
- after do
44
- @store = nil
45
- @entity_store = nil
46
- end
47
-
48
- # Low-level implementation methods ===========================================
49
-
50
- it 'writes a list of negotation tuples with #write' do
51
- lambda { @store.write('/test', [[{}, {}]]) }.should.not.raise
52
- end
53
-
54
- it 'reads a list of negotation tuples with #read' do
55
- @store.write('/test', [[{},{}],[{},{}]])
56
- tuples = @store.read('/test')
57
- tuples.should.equal [ [{},{}], [{},{}] ]
58
- end
59
-
60
- it 'reads an empty list with #read when nothing cached at key' do
61
- @store.read('/nothing').should.be.empty
62
- end
63
-
64
- it 'removes entries for key with #purge' do
65
- @store.write('/test', [[{},{}]])
66
- @store.read('/test').should.not.be.empty
67
-
68
- @store.purge('/test')
69
- @store.read('/test').should.be.empty
70
- end
71
-
72
- it 'succeeds when purging non-existing entries' do
73
- @store.read('/test').should.be.empty
74
- @store.purge('/test')
75
- end
76
-
77
- it 'returns nil from #purge' do
78
- @store.write('/test', [[{},{}]])
79
- @store.purge('/test').should.be.nil
80
- @store.read('/test').should.equal []
81
- end
82
-
83
- %w[/test http://example.com:8080/ /test?x=y /test?x=y&p=q].each do |key|
84
- it "can read and write key: '#{key}'" do
85
- lambda { @store.write(key, [[{},{}]]) }.should.not.raise
86
- @store.read(key).should.equal [[{},{}]]
87
- end
88
- end
89
-
90
- it "can read and write fairly large keys" do
91
- key = "b" * 4096
92
- lambda { @store.write(key, [[{},{}]]) }.should.not.raise
93
- @store.read(key).should.equal [[{},{}]]
94
- end
95
-
96
- it "allows custom cache keys from block" do
97
- request = mock_request('/test', {})
98
- request.env['rack-cache.cache_key'] =
99
- lambda { |request| request.path_info.reverse }
100
- @store.cache_key(request).should == 'tset/'
101
- end
102
-
103
- it "allows custom cache keys from class" do
104
- request = mock_request('/test', {})
105
- request.env['rack-cache.cache_key'] = Class.new do
106
- def self.call(request); request.path_info.reverse end
107
- end
108
- @store.cache_key(request).should == 'tset/'
109
- end
110
-
111
- it 'does not blow up when given a non-marhsalable object with an ALL_CAPS key' do
112
- store_simple_entry('/bad', { 'SOME_THING' => Proc.new {} })
113
- end
114
-
115
- # Abstract methods ===========================================================
116
-
117
- it 'stores a cache entry' do
118
- cache_key = store_simple_entry
119
- @store.read(cache_key).should.not.be.empty
120
- end
121
-
122
- it 'sets the X-Content-Digest response header before storing' do
123
- cache_key = store_simple_entry
124
- req, res = @store.read(cache_key).first
125
- res['X-Content-Digest'].should.equal 'a94a8fe5ccb19ba61c4c0873d391e987982fbbd3'
126
- end
127
-
128
- it 'finds a stored entry with #lookup' do
129
- store_simple_entry
130
- response = @store.lookup(@request, @entity_store)
131
- response.should.not.be.nil
132
- response.should.be.kind_of Rack::Cache::Response
133
- end
134
-
135
- it 'does not find an entry with #lookup when none exists' do
136
- req = mock_request('/test', {'HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar'})
137
- @store.lookup(req, @entity_store).should.be.nil
138
- end
139
-
140
- it "canonizes urls for cache keys" do
141
- store_simple_entry(path='/test?x=y&p=q')
142
-
143
- hits_req = mock_request(path, {})
144
- miss_req = mock_request('/test?p=x', {})
145
-
146
- @store.lookup(hits_req, @entity_store).should.not.be.nil
147
- @store.lookup(miss_req, @entity_store).should.be.nil
148
- end
149
-
150
- it 'does not find an entry with #lookup when the body does not exist' do
151
- store_simple_entry
152
- @response.headers['X-Content-Digest'].should.not.be.nil
153
- @entity_store.purge(@response.headers['X-Content-Digest'])
154
- @store.lookup(@request, @entity_store).should.be.nil
155
- end
156
-
157
- it 'restores response headers properly with #lookup' do
158
- store_simple_entry
159
- response = @store.lookup(@request, @entity_store)
160
- response.headers.
161
- should.equal @response.headers.merge('Content-Length' => '4')
162
- end
163
-
164
- it 'restores response body from entity store with #lookup' do
165
- store_simple_entry
166
- response = @store.lookup(@request, @entity_store)
167
- body = '' ; response.body.each {|p| body << p}
168
- body.should.equal 'test'
169
- end
170
-
171
- it 'invalidates meta and entity store entries with #invalidate' do
172
- store_simple_entry
173
- @store.invalidate(@request, @entity_store)
174
- response = @store.lookup(@request, @entity_store)
175
- response.should.be.kind_of Rack::Cache::Response
176
- response.should.not.be.fresh
177
- end
178
-
179
- it 'succeeds quietly when #invalidate called with no matching entries' do
180
- req = mock_request('/test', {})
181
- @store.invalidate(req, @entity_store)
182
- @store.lookup(@request, @entity_store).should.be.nil
183
- end
184
-
185
- # Vary =======================================================================
186
-
187
- it 'does not return entries that Vary with #lookup' do
188
- req1 = mock_request('/test', {'HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar'})
189
- req2 = mock_request('/test', {'HTTP_FOO' => 'Bling', 'HTTP_BAR' => 'Bam'})
190
- res = mock_response(200, {'Vary' => 'Foo Bar'}, ['test'])
191
- @store.store(req1, res, @entity_store)
192
-
193
- @store.lookup(req2, @entity_store).should.be.nil
194
- end
195
-
196
- it 'stores multiple responses for each Vary combination' do
197
- req1 = mock_request('/test', {'HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar'})
198
- res1 = mock_response(200, {'Vary' => 'Foo Bar'}, ['test 1'])
199
- key = @store.store(req1, res1, @entity_store)
200
-
201
- req2 = mock_request('/test', {'HTTP_FOO' => 'Bling', 'HTTP_BAR' => 'Bam'})
202
- res2 = mock_response(200, {'Vary' => 'Foo Bar'}, ['test 2'])
203
- @store.store(req2, res2, @entity_store)
204
-
205
- req3 = mock_request('/test', {'HTTP_FOO' => 'Baz', 'HTTP_BAR' => 'Boom'})
206
- res3 = mock_response(200, {'Vary' => 'Foo Bar'}, ['test 3'])
207
- @store.store(req3, res3, @entity_store)
208
-
209
- slurp(@store.lookup(req3, @entity_store).body).should.equal 'test 3'
210
- slurp(@store.lookup(req1, @entity_store).body).should.equal 'test 1'
211
- slurp(@store.lookup(req2, @entity_store).body).should.equal 'test 2'
212
-
213
- @store.read(key).length.should.equal 3
214
- end
215
-
216
- it 'overwrites non-varying responses with #store' do
217
- req1 = mock_request('/test', {'HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar'})
218
- res1 = mock_response(200, {'Vary' => 'Foo Bar'}, ['test 1'])
219
- key = @store.store(req1, res1, @entity_store)
220
- slurp(@store.lookup(req1, @entity_store).body).should.equal 'test 1'
221
-
222
- req2 = mock_request('/test', {'HTTP_FOO' => 'Bling', 'HTTP_BAR' => 'Bam'})
223
- res2 = mock_response(200, {'Vary' => 'Foo Bar'}, ['test 2'])
224
- @store.store(req2, res2, @entity_store)
225
- slurp(@store.lookup(req2, @entity_store).body).should.equal 'test 2'
226
-
227
- req3 = mock_request('/test', {'HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar'})
228
- res3 = mock_response(200, {'Vary' => 'Foo Bar'}, ['test 3'])
229
- @store.store(req3, res3, @entity_store)
230
- slurp(@store.lookup(req1, @entity_store).body).should.equal 'test 3'
231
-
232
- @store.read(key).length.should.equal 2
233
- end
234
- end
235
-
236
-
237
- describe 'Rack::Cache::MetaStore' do
238
- describe 'Heap' do
239
- before do
240
- @store = Rack::Cache::MetaStore::Heap.new
241
- @entity_store = Rack::Cache::EntityStore::Heap.new
242
- end
243
- behaves_like 'A Rack::Cache::MetaStore Implementation'
244
- end
245
-
246
- describe 'Disk' do
247
- before do
248
- @temp_dir = create_temp_directory
249
- @store = Rack::Cache::MetaStore::Disk.new("#{@temp_dir}/meta")
250
- @entity_store = Rack::Cache::EntityStore::Disk.new("#{@temp_dir}/entity")
251
- end
252
- after do
253
- remove_entry_secure @temp_dir
254
- end
255
- behaves_like 'A Rack::Cache::MetaStore Implementation'
256
- end
257
-
258
- need_memcached 'metastore tests' do
259
- describe 'MemCached' do
260
- before do
261
- @temp_dir = create_temp_directory
262
- $memcached.flush
263
- @store = Rack::Cache::MetaStore::MemCached.new($memcached)
264
- @entity_store = Rack::Cache::EntityStore::Heap.new
265
- end
266
- behaves_like 'A Rack::Cache::MetaStore Implementation'
267
- end
268
-
269
- describe 'options parsing' do
270
- before do
271
- uri = URI.parse("memcached://#{ENV['MEMCACHED']}/meta_ns1?show_backtraces=true")
272
- @memcached_metastore = Rack::Cache::MetaStore::MemCached.resolve uri
273
- end
274
-
275
- it 'passes options from uri' do
276
- @memcached_metastore.cache.instance_variable_get(:@options)[:show_backtraces].should.equal true
277
- end
278
-
279
- it 'takes namespace into account' do
280
- @memcached_metastore.cache.instance_variable_get(:@options)[:prefix_key].should.equal 'meta_ns1'
281
- end
282
- end
283
- end
284
-
285
- need_dalli 'metastore tests' do
286
- describe 'Dalli' do
287
- before do
288
- @temp_dir = create_temp_directory
289
- $dalli.flush_all
290
- @store = Rack::Cache::MetaStore::Dalli.new($dalli)
291
- @entity_store = Rack::Cache::EntityStore::Heap.new
292
- end
293
- behaves_like 'A Rack::Cache::MetaStore Implementation'
294
- end
295
-
296
- describe 'options parsing' do
297
- before do
298
- uri = URI.parse("memcached://#{ENV['MEMCACHED']}/meta_ns1?show_backtraces=true")
299
- @dalli_metastore = Rack::Cache::MetaStore::Dalli.resolve uri
300
- end
301
-
302
- it 'passes options from uri' do
303
- @dalli_metastore.cache.instance_variable_get(:@options)[:show_backtraces].should.equal true
304
- end
305
-
306
- it 'takes namespace into account' do
307
- @dalli_metastore.cache.instance_variable_get(:@options)[:namespace].should.equal 'meta_ns1'
308
- end
309
- end
310
- end
311
-
312
- need_java 'entity store testing' do
313
- module Rack::Cache::AppEngine
314
- module MC
315
- class << (Service = {})
316
-
317
- def contains(key); include?(key); end
318
- def get(key); self[key]; end;
319
- def put(key, value, ttl = nil)
320
- self[key] = value
321
- end
322
-
323
- end
324
- end
325
- end
326
-
327
- describe 'GAEStore' do
328
- before :each do
329
- Rack::Cache::AppEngine::MC::Service.clear
330
- @store = Rack::Cache::MetaStore::GAEStore.new
331
- @entity_store = Rack::Cache::EntityStore::Heap.new
332
- end
333
- behaves_like 'A Rack::Cache::MetaStore Implementation'
334
- end
335
-
336
- end
337
-
338
- end