rack-cache 1.2 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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