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