josh-rack-cache 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,77 @@
1
+ require "#{File.dirname(__FILE__)}/spec_setup"
2
+ require 'rack/cache/options'
3
+
4
+ module Rack::Cache::Options
5
+ option_accessor :foo
6
+ end
7
+
8
+ class MockOptions
9
+ include Rack::Cache::Options
10
+ def initialize
11
+ @env = nil
12
+ initialize_options
13
+ end
14
+ end
15
+
16
+ describe 'Rack::Cache::Options' do
17
+ before { @options = MockOptions.new }
18
+
19
+ describe '#set' do
20
+ it 'sets a Symbol option as rack-cache.symbol' do
21
+ @options.set :bar, 'baz'
22
+ @options.options['rack-cache.bar'].should.equal 'baz'
23
+ end
24
+ it 'sets a String option as string' do
25
+ @options.set 'foo.bar', 'bling'
26
+ @options.options['foo.bar'].should.equal 'bling'
27
+ end
28
+ it 'sets all key/value pairs when given a Hash' do
29
+ @options.set :foo => 'bar', :bar => 'baz', 'foo.bar' => 'bling'
30
+ @options.foo.should.equal 'bar'
31
+ @options.options['rack-cache.bar'].should.equal 'baz'
32
+ @options.options['foo.bar'].should.equal 'bling'
33
+ end
34
+ end
35
+
36
+ it 'makes options declared with option_accessor available as attributes' do
37
+ @options.set :foo, 'bar'
38
+ @options.foo.should.equal 'bar'
39
+ end
40
+
41
+ it 'allows setting multiple options via assignment' do
42
+ @options.options = { :foo => 'bar', :bar => 'baz', 'foo.bar' => 'bling' }
43
+ @options.foo.should.equal 'bar'
44
+ @options.options['foo.bar'].should.equal 'bling'
45
+ @options.options['rack-cache.bar'].should.equal 'baz'
46
+ end
47
+
48
+ it "allows storing the value as a block" do
49
+ block = Proc.new { "bar block" }
50
+ @options.set(:foo, &block)
51
+ @options.options['rack-cache.foo'].should.equal block
52
+ end
53
+
54
+ it 'allows the cache key generator to be configured' do
55
+ @options.should.respond_to :cache_key
56
+ @options.should.respond_to :cache_key=
57
+ end
58
+
59
+ it 'allows the meta store to be configured' do
60
+ @options.should.respond_to :metastore
61
+ @options.should.respond_to :metastore=
62
+ @options.metastore.should.not.be nil
63
+ end
64
+
65
+ it 'allows the entity store to be configured' do
66
+ @options.should.respond_to :entitystore
67
+ @options.should.respond_to :entitystore=
68
+ @options.entitystore.should.not.be nil
69
+ end
70
+
71
+ it 'allows log verbosity to be configured' do
72
+ @options.should.respond_to :verbose
73
+ @options.should.respond_to :verbose=
74
+ @options.should.respond_to :verbose?
75
+ @options.verbose.should.not.be.nil
76
+ end
77
+ end
Binary file
@@ -0,0 +1,19 @@
1
+ require "#{File.dirname(__FILE__)}/spec_setup"
2
+ require 'rack/cache/request'
3
+
4
+ describe 'Rack::Cache::Request' do
5
+ it 'is marked as no_cache when the Cache-Control header includes the no-cache directive' do
6
+ request = Rack::Cache::Request.new('HTTP_CACHE_CONTROL' => 'public, no-cache')
7
+ assert request.no_cache?
8
+ end
9
+
10
+ it 'is marked as no_cache when request should not be loaded from cache' do
11
+ request = Rack::Cache::Request.new('HTTP_PRAGMA' => 'no-cache')
12
+ assert request.no_cache?
13
+ end
14
+
15
+ it 'is not marked as no_cache when neither no-cache directive is specified' do
16
+ request = Rack::Cache::Request.new('HTTP_CACHE_CONTROL' => 'public')
17
+ assert !request.no_cache?
18
+ end
19
+ end
@@ -0,0 +1,178 @@
1
+ require "#{File.dirname(__FILE__)}/spec_setup"
2
+
3
+ describe 'Rack::Cache::Response' do
4
+ before :each do
5
+ @now = Time.httpdate(Time.now.httpdate)
6
+ @one_hour_ago = Time.httpdate((Time.now - (60**2)).httpdate)
7
+ @one_hour_later = Time.httpdate((Time.now + (60**2)).httpdate)
8
+ @res = Rack::Cache::Response.new(200, {'Date' => @now.httpdate}, [])
9
+ end
10
+
11
+ after :each do
12
+ @now, @res, @one_hour_ago = nil
13
+ end
14
+
15
+ it 'responds to #to_a with a Rack response tuple' do
16
+ @res.should.respond_to :to_a
17
+ @res.to_a.should.equal [200, {'Date' => @now.httpdate}, []]
18
+ end
19
+
20
+ describe '#cache_control' do
21
+ it 'handles multiple name=value pairs' do
22
+ @res.headers['Cache-Control'] = 'max-age=600, max-stale=300, min-fresh=570'
23
+ @res.cache_control['max-age'].should.equal '600'
24
+ @res.cache_control['max-stale'].should.equal '300'
25
+ @res.cache_control['min-fresh'].should.equal '570'
26
+ end
27
+ it 'removes the header when given an empty hash' do
28
+ @res.headers['Cache-Control'] = 'max-age=600, must-revalidate'
29
+ @res.cache_control['max-age'].should.equal '600'
30
+ @res.cache_control = {}
31
+ @res.headers.should.not.include 'Cache-Control'
32
+ end
33
+ end
34
+
35
+ describe '#validateable?' do
36
+ it 'is true when Last-Modified header present' do
37
+ @res = Rack::Cache::Response.new(200, {'Last-Modified' => @one_hour_ago.httpdate}, [])
38
+ @res.should.be.validateable
39
+ end
40
+ it 'is true when ETag header present' do
41
+ @res = Rack::Cache::Response.new(200, {'ETag' => '"12345"'}, [])
42
+ @res.should.be.validateable
43
+ end
44
+ it 'is false when no validator is present' do
45
+ @res = Rack::Cache::Response.new(200, {}, [])
46
+ @res.should.not.be.validateable
47
+ end
48
+ end
49
+
50
+ describe '#date' do
51
+ it 'uses the Date header if present' do
52
+ @res = Rack::Cache::Response.new(200, {'Date' => @one_hour_ago.httpdate}, [])
53
+ @res.date.should.equal @one_hour_ago
54
+ end
55
+ it 'uses the current time when no Date header present' do
56
+ @res = Rack::Cache::Response.new(200, {}, [])
57
+ @res.date.should.be.close Time.now, 1
58
+ end
59
+ it 'returns the correct date when the header is modified directly' do
60
+ @res = Rack::Cache::Response.new(200, { 'Date' => @one_hour_ago.httpdate }, [])
61
+ @res.date.should.equal @one_hour_ago
62
+ @res.headers['Date'] = @now.httpdate
63
+ @res.date.should.equal @now
64
+ end
65
+ end
66
+
67
+ describe '#max_age' do
68
+ it 'uses s-maxage cache control directive when present' do
69
+ @res.headers['Cache-Control'] = 's-maxage=600, max-age=0'
70
+ @res.max_age.should.equal 600
71
+ end
72
+ it 'falls back to max-age when no s-maxage directive present' do
73
+ @res.headers['Cache-Control'] = 'max-age=600'
74
+ @res.max_age.should.equal 600
75
+ end
76
+ it 'falls back to Expires when no max-age or s-maxage directive present' do
77
+ @res.headers['Cache-Control'] = 'must-revalidate'
78
+ @res.headers['Expires'] = @one_hour_later.httpdate
79
+ @res.max_age.should.equal 60 ** 2
80
+ end
81
+ it 'gives a #max_age of nil when no freshness information available' do
82
+ @res.max_age.should.be.nil
83
+ end
84
+ end
85
+
86
+ describe '#private=' do
87
+ it 'adds the private Cache-Control directive when set true' do
88
+ @res.headers['Cache-Control'] = 'max-age=100'
89
+ @res.private = true
90
+ @res.headers['Cache-Control'].split(', ').sort.
91
+ should.equal ['max-age=100', 'private']
92
+ end
93
+ it 'removes the public Cache-Control directive' do
94
+ @res.headers['Cache-Control'] = 'public, max-age=100'
95
+ @res.private = true
96
+ @res.headers['Cache-Control'].split(', ').sort.
97
+ should.equal ['max-age=100', 'private']
98
+ end
99
+ end
100
+
101
+ describe '#expire!' do
102
+ it 'sets the Age to be equal to the max-age' do
103
+ @res.headers['Cache-Control'] = 'max-age=100'
104
+ @res.expire!
105
+ @res.headers['Age'].should.equal '100'
106
+ end
107
+ it 'sets the Age to be equal to the s-maxage when both max-age and s-maxage present' do
108
+ @res.headers['Cache-Control'] = 'max-age=100, s-maxage=500'
109
+ @res.expire!
110
+ @res.headers['Age'].should.equal '500'
111
+ end
112
+ it 'does nothing when the response is already stale/expired' do
113
+ @res.headers['Cache-Control'] = 'max-age=5, s-maxage=500'
114
+ @res.headers['Age'] = '1000'
115
+ @res.expire!
116
+ @res.headers['Age'].should.equal '1000'
117
+ end
118
+ it 'does nothing when the response does not include freshness information' do
119
+ @res.expire!
120
+ @res.headers.should.not.include 'Age'
121
+ end
122
+ end
123
+
124
+ describe '#ttl' do
125
+ it 'is nil when no Expires or Cache-Control headers present' do
126
+ @res.ttl.should.be.nil
127
+ end
128
+ it 'uses the Expires header when no max-age is present' do
129
+ @res.headers['Expires'] = (@res.now + (60**2)).httpdate
130
+ @res.ttl.should.be.close(60**2, 1)
131
+ end
132
+ it 'returns negative values when Expires is in part' do
133
+ @res.ttl.should.be.nil
134
+ @res.headers['Expires'] = @one_hour_ago.httpdate
135
+ @res.ttl.should.be < 0
136
+ end
137
+ it 'uses the Cache-Control max-age value when present' do
138
+ @res.headers['Cache-Control'] = 'max-age=60'
139
+ @res.ttl.should.be.close(60, 1)
140
+ end
141
+ end
142
+
143
+ describe '#vary' do
144
+ it 'is nil when no Vary header is present' do
145
+ @res.vary.should.be.nil
146
+ end
147
+ it 'returns the literal value of the Vary header' do
148
+ @res.headers['Vary'] = 'Foo Bar Baz'
149
+ @res.vary.should.equal 'Foo Bar Baz'
150
+ end
151
+ it 'can be checked for existence using the #vary? method' do
152
+ @res.should.respond_to :vary?
153
+ @res.should.not.vary
154
+ @res.headers['Vary'] = '*'
155
+ @res.should.vary
156
+ end
157
+ end
158
+
159
+ describe '#vary_header_names' do
160
+ it 'returns an empty Array when no Vary header is present' do
161
+ @res.vary_header_names.should.be.empty
162
+ end
163
+ it 'parses a single header name value' do
164
+ @res.headers['Vary'] = 'Accept-Language'
165
+ @res.vary_header_names.should.equal ['Accept-Language']
166
+ end
167
+ it 'parses multiple header name values separated by spaces' do
168
+ @res.headers['Vary'] = 'Accept-Language User-Agent X-Foo'
169
+ @res.vary_header_names.should.equal \
170
+ ['Accept-Language', 'User-Agent', 'X-Foo']
171
+ end
172
+ it 'parses multiple header name values separated by commas' do
173
+ @res.headers['Vary'] = 'Accept-Language,User-Agent, X-Foo'
174
+ @res.vary_header_names.should.equal \
175
+ ['Accept-Language', 'User-Agent', 'X-Foo']
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,237 @@
1
+ require 'pp'
2
+ require 'tmpdir'
3
+ require 'stringio'
4
+
5
+ [ STDOUT, STDERR ].each { |io| io.sync = true }
6
+
7
+ begin
8
+ require 'test/spec'
9
+ rescue LoadError => boom
10
+ require 'rubygems' rescue nil
11
+ require 'test/spec'
12
+ end
13
+
14
+ # Set the MEMCACHED environment variable as follows to enable testing
15
+ # of the MemCached meta and entity stores.
16
+ ENV['MEMCACHED'] ||= 'localhost:11211'
17
+ $memcached = nil
18
+ $memcache = nil
19
+
20
+ def have_memcached?(server=ENV['MEMCACHED'])
21
+ return $memcached unless $memcached.nil?
22
+ v, $VERBOSE = $VERBOSE, nil # silence warnings from memcached
23
+ require 'memcached'
24
+ $VERBOSE = v
25
+ $memcached = Memcached.new(server)
26
+ $memcached.set('ping', '')
27
+ true
28
+ rescue LoadError => boom
29
+ $memcached = false
30
+ false
31
+ rescue => boom
32
+ STDERR.puts "memcached not working. related tests will be skipped."
33
+ $memcached = false
34
+ false
35
+ end
36
+
37
+ have_memcached?
38
+
39
+ def have_memcache?(server=ENV['MEMCACHED'])
40
+ return $memcache unless $memcache.nil?
41
+ require 'memcache'
42
+ $memcache = MemCache.new(server)
43
+ $memcache.set('ping', '')
44
+ true
45
+ rescue LoadError => boom
46
+ $memcache = false
47
+ false
48
+ rescue => boom
49
+ STDERR.puts "memcache not working. related tests will be skipped."
50
+ $memcache = false
51
+ false
52
+ end
53
+
54
+ have_memcache?
55
+
56
+ def need_memcached(forwhat)
57
+ if have_memcached?
58
+ yield
59
+ else
60
+ STDERR.puts "skipping memcached #{forwhat}"
61
+ end
62
+ end
63
+
64
+ def need_memcache(forwhat)
65
+ if have_memcache?
66
+ yield
67
+ else
68
+ STDERR.puts "skipping memcache #{forwhat}"
69
+ end
70
+ end
71
+
72
+ def need_java(forwhat)
73
+ if RUBY_PLATFORM =~ /java/
74
+ yield
75
+ else
76
+ STDERR.puts "skipping app engine #{forwhat}"
77
+ end
78
+ end
79
+
80
+
81
+ # Setup the load path ..
82
+ $LOAD_PATH.unshift File.dirname(File.dirname(__FILE__)) + '/lib'
83
+ $LOAD_PATH.unshift File.dirname(__FILE__)
84
+
85
+ require 'rack/cache'
86
+
87
+
88
+ # Methods for constructing downstream applications / response
89
+ # generators.
90
+ module CacheContextHelpers
91
+
92
+ # The Rack::Cache::Context instance used for the most recent
93
+ # request.
94
+ attr_reader :cache
95
+
96
+ # An Array of Rack::Cache::Context instances used for each request, in
97
+ # request order.
98
+ attr_reader :caches
99
+
100
+ # The Rack::Response instance result of the most recent request.
101
+ attr_reader :response
102
+
103
+ # An Array of Rack::Response instances for each request, in request order.
104
+ attr_reader :responses
105
+
106
+ # The backend application object.
107
+ attr_reader :app
108
+
109
+ def setup_cache_context
110
+ # holds each Rack::Cache::Context
111
+ @app = nil
112
+
113
+ # each time a request is made, a clone of @cache_template is used
114
+ # and appended to @caches.
115
+ @cache_template = nil
116
+ @cache = nil
117
+ @caches = []
118
+ @errors = StringIO.new
119
+ @cache_config = nil
120
+
121
+ @called = false
122
+ @request = nil
123
+ @response = nil
124
+ @responses = []
125
+
126
+ @storage = Rack::Cache::Storage.new
127
+ end
128
+
129
+ def teardown_cache_context
130
+ @app, @cache_template, @cache, @caches, @called,
131
+ @request, @response, @responses, @cache_config = nil
132
+ end
133
+
134
+ # A basic response with 200 status code and a tiny body.
135
+ def respond_with(status=200, headers={}, body=['Hello World'])
136
+ called = false
137
+ @app =
138
+ lambda do |env|
139
+ called = true
140
+ response = Rack::Response.new(body, status, headers)
141
+ request = Rack::Request.new(env)
142
+ yield request, response if block_given?
143
+ response.finish
144
+ end
145
+ @app.meta_def(:called?) { called }
146
+ @app.meta_def(:reset!) { called = false }
147
+ @app
148
+ end
149
+
150
+ def cache_config(&block)
151
+ @cache_config = block
152
+ end
153
+
154
+ def request(method, uri='/', opts={})
155
+ opts = {
156
+ 'rack.run_once' => true,
157
+ 'rack.errors' => @errors,
158
+ 'rack-cache.storage' => @storage
159
+ }.merge(opts)
160
+
161
+ fail 'response not specified (use respond_with)' if @app.nil?
162
+ @app.reset! if @app.respond_to?(:reset!)
163
+
164
+ @cache_prototype ||= Rack::Cache::Context.new(@app, &@cache_config)
165
+ @cache = @cache_prototype.clone
166
+ @caches << @cache
167
+ @request = Rack::MockRequest.new(@cache)
168
+ yield @cache if block_given?
169
+ @response = @request.request(method.to_s.upcase, uri, opts)
170
+ @responses << @response
171
+ @response
172
+ end
173
+
174
+ def get(stem, env={}, &b)
175
+ request(:get, stem, env, &b)
176
+ end
177
+
178
+ def head(stem, env={}, &b)
179
+ request(:head, stem, env, &b)
180
+ end
181
+
182
+ def post(*args, &b)
183
+ request(:post, *args, &b)
184
+ end
185
+ end
186
+
187
+
188
+ module TestHelpers
189
+ include FileUtils
190
+ F = File
191
+
192
+ @@temp_dir_count = 0
193
+
194
+ def create_temp_directory
195
+ @@temp_dir_count += 1
196
+ path = F.join(Dir.tmpdir, "rack-cache-#{$$}-#{@@temp_dir_count}")
197
+ mkdir_p path
198
+ if block_given?
199
+ yield path
200
+ remove_entry_secure path
201
+ end
202
+ path
203
+ end
204
+
205
+ def create_temp_file(root, file, data='')
206
+ path = F.join(root, file)
207
+ mkdir_p F.dirname(path)
208
+ F.open(path, 'w') { |io| io.write(data) }
209
+ end
210
+
211
+ end
212
+
213
+ class Test::Unit::TestCase
214
+ include TestHelpers
215
+ include CacheContextHelpers
216
+ end
217
+
218
+ # Metaid == a few simple metaclass helper
219
+ # (See http://whytheluckystiff.net/articles/seeingMetaclassesClearly.html.)
220
+ class Object
221
+ # The hidden singleton lurks behind everyone
222
+ def metaclass; class << self; self; end; end
223
+ def meta_eval(&blk); metaclass.instance_eval(&blk); end
224
+ # Adds methods to a metaclass
225
+ def meta_def name, &blk
226
+ meta_eval { define_method name, &blk }
227
+ end
228
+ # Defines an instance method within a class
229
+ def class_def name, &blk
230
+ class_eval { define_method name, &blk }
231
+ end
232
+
233
+ # True when the Object is neither false or nil.
234
+ def truthy?
235
+ !!self
236
+ end
237
+ end