test/context_test.rb in rtomayko-rack-cache-0.2.0 vs test/context_test.rb in rtomayko-rack-cache-0.3.0

- old
+ new

@@ -13,28 +13,62 @@ response.should.be.ok cache.should.a.performed :pass response.headers.should.not.include 'Age' end - it 'passes on requests with Authorization' do - respond_with 200 + it 'does not cache with Authorization request header and non public response' do + respond_with 200, 'Etag' => '"FOO"' get '/', 'HTTP_AUTHORIZATION' => 'basic foobarbaz' app.should.be.called response.should.be.ok - cache.should.a.performed :pass + response.headers['Cache-Control'].should.equal 'private' + cache.should.a.performed :miss + cache.should.a.performed :fetch + cache.should.a.not.performed :store + cache.should.a.performed :deliver response.headers.should.not.include 'Age' end - it 'passes on requests with a Cookie' do + it 'does cache with Authorization request header and public response' do + respond_with 200, 'Cache-Control' => 'public', 'ETag' => '"FOO"' + get '/', 'HTTP_AUTHORIZATION' => 'basic foobarbaz' + + app.should.be.called + response.should.be.ok + cache.should.a.performed :miss + cache.should.a.performed :fetch + cache.should.a.performed :store + response.headers.should.include 'Age' + response.headers['Cache-Control'].should.equal 'public' + end + + it 'does not cache with Cookie header and non public response' do + respond_with 200, 'Etag' => '"FOO"' + get '/', 'HTTP_COOKIE' => 'foo=bar' + + app.should.be.called + response.should.be.ok + response.headers['Cache-Control'].should.equal 'private' + cache.should.a.performed :miss + cache.should.a.performed :fetch + cache.should.a.not.performed :store + cache.should.a.performed :deliver + response.headers.should.not.include 'Age' + end + + it 'does not cache requests with a Cookie header' do respond_with 200 get '/', 'HTTP_COOKIE' => 'foo=bar' response.should.be.ok app.should.be.called - cache.should.a.performed :pass + cache.should.a.performed :miss + cache.should.a.performed :fetch + cache.should.a.performed :deliver response.headers.should.not.include 'Age' + response.headers['Cache-Control'].should.equal 'private' end it 'responds with 304 when If-Modified-Since matches Last-Modified' do timestamp = Time.now.httpdate respond_with do |req,res| @@ -45,11 +79,11 @@ end get '/', 'HTTP_IF_MODIFIED_SINCE' => timestamp app.should.be.called - response.status.should.be == 304 + response.status.should.equal 304 response.headers.should.not.include 'Content-Length' response.headers.should.not.include 'Content-Type' response.body.should.empty cache.should.a.performed :miss cache.should.a.performed :store @@ -64,11 +98,11 @@ end get '/', 'HTTP_IF_NONE_MATCH' => '12345' app.should.be.called - response.status.should.be == 304 + response.status.should.equal 304 response.headers.should.not.include 'Content-Length' response.headers.should.not.include 'Content-Type' response.headers.should.include 'Etag' response.body.should.empty cache.should.a.performed :miss @@ -99,11 +133,11 @@ it "does not cache #{response_code} responses" do respond_with response_code, 'Expires' => (Time.now + 5).httpdate get '/' cache.should.a.not.performed :store - response.status.should.be == response_code + response.status.should.equal response_code response.headers.should.not.include 'Age' end end end @@ -140,49 +174,63 @@ it 'caches responses with an Expiration header' do respond_with 200, 'Expires' => (Time.now + 5).httpdate get '/' response.should.be.ok - response.body.should.be == 'Hello World' + response.body.should.equal 'Hello World' response.headers.should.include 'Date' response['Age'].should.not.be.nil response['X-Content-Digest'].should.not.be.nil cache.should.a.performed :miss cache.should.a.performed :store - cache.metastore.to_hash.keys.length.should.be == 1 + cache.metastore.to_hash.keys.length.should.equal 1 end it 'caches responses with a max-age directive' do respond_with 200, 'Cache-Control' => 'max-age=5' get '/' response.should.be.ok - response.body.should.be == 'Hello World' + response.body.should.equal 'Hello World' response.headers.should.include 'Date' response['Age'].should.not.be.nil response['X-Content-Digest'].should.not.be.nil cache.should.a.performed :miss cache.should.a.performed :store - cache.metastore.to_hash.keys.length.should.be == 1 + cache.metastore.to_hash.keys.length.should.equal 1 end + it 'caches responses with a s-maxage directive' do + respond_with 200, 'Cache-Control' => 's-maxage=5' + get '/' + + response.should.be.ok + response.body.should.equal 'Hello World' + response.headers.should.include 'Date' + response['Age'].should.not.be.nil + response['X-Content-Digest'].should.not.be.nil + cache.should.a.performed :miss + cache.should.a.performed :store + cache.metastore.to_hash.keys.length.should.equal 1 + end + it 'caches responses with a Last-Modified validator but no freshness information' do respond_with 200, 'Last-Modified' => Time.now.httpdate get '/' response.should.be.ok - response.body.should.be == 'Hello World' + response.body.should.equal 'Hello World' cache.should.a.performed :miss cache.should.a.performed :store end it 'caches responses with an ETag validator but no freshness information' do respond_with 200, 'Etag' => '"123456"' get '/' response.should.be.ok - response.body.should.be == 'Hello World' + response.body.should.equal 'Hello World' cache.should.a.performed :miss cache.should.a.performed :store end it 'hits cached response with Expires header' do @@ -194,21 +242,21 @@ app.should.be.called response.should.be.ok response.headers.should.include 'Date' cache.should.a.performed :miss cache.should.a.performed :store - response.body.should.be == 'Hello World' + response.body.should.equal 'Hello World' get '/' response.should.be.ok app.should.not.be.called - response['Date'].should.be == responses.first['Date'] - response['Age'].to_i.should.be > 0 + response['Date'].should.equal responses.first['Date'] + response['Age'].to_i.should.satisfy { |age| age > 0 } response['X-Content-Digest'].should.not.be.nil cache.should.a.performed :hit cache.should.a.not.performed :fetch - response.body.should.be == 'Hello World' + response.body.should.equal 'Hello World' end it 'hits cached response with max-age directive' do respond_with 200, 'Date' => (Time.now - 5).httpdate, @@ -218,23 +266,79 @@ app.should.be.called response.should.be.ok response.headers.should.include 'Date' cache.should.a.performed :miss cache.should.a.performed :store - response.body.should.be == 'Hello World' + response.body.should.equal 'Hello World' get '/' response.should.be.ok app.should.not.be.called - response['Date'].should.be == responses.first['Date'] - response['Age'].to_i.should.be > 0 + response['Date'].should.equal responses.first['Date'] + response['Age'].to_i.should.satisfy { |age| age > 0 } response['X-Content-Digest'].should.not.be.nil cache.should.a.performed :hit cache.should.a.not.performed :fetch - response.body.should.be == 'Hello World' + response.body.should.equal 'Hello World' end + it 'hits cached response with s-maxage directive' do + respond_with 200, + 'Date' => (Time.now - 5).httpdate, + 'Cache-Control' => 's-maxage=10, max-age=0' + + get '/' + app.should.be.called + response.should.be.ok + response.headers.should.include 'Date' + cache.should.a.performed :miss + cache.should.a.performed :store + response.body.should.equal 'Hello World' + + get '/' + response.should.be.ok + app.should.not.be.called + response['Date'].should.equal responses.first['Date'] + response['Age'].to_i.should.satisfy { |age| age > 0 } + response['X-Content-Digest'].should.not.be.nil + cache.should.a.performed :hit + cache.should.a.not.performed :fetch + response.body.should.equal 'Hello World' + end + + it 'assigns default_ttl when response has no freshness information' do + respond_with 200 + + get '/', 'rack-cache.default_ttl' => 10 + app.should.be.called + response.should.be.ok + cache.should.a.performed :miss + cache.should.a.performed :store + response.body.should.equal 'Hello World' + response['Cache-Control'].should.include 's-maxage=10' + + get '/', 'rack-cache.default_ttl' => 10 + response.should.be.ok + app.should.not.be.called + cache.should.a.performed :hit + cache.should.a.not.performed :fetch + response.body.should.equal 'Hello World' + end + + it 'does not assign default_ttl when response has must-revalidate directive' do + respond_with 200, + 'Cache-Control' => 'must-revalidate' + + get '/', 'rack-cache.default_ttl' => 10 + app.should.be.called + response.should.be.ok + cache.should.a.performed :miss + cache.should.a.not.performed :store + response['Cache-Control'].should.not.include 's-maxage' + response.body.should.equal 'Hello World' + end + it 'fetches full response when cache stale and no validators present' do respond_with 200, 'Expires' => (Time.now + 5).httpdate # build initial request get '/' @@ -243,27 +347,27 @@ response.headers.should.include 'Date' response.headers.should.include 'X-Content-Digest' response.headers.should.include 'Age' cache.should.a.performed :miss cache.should.a.performed :store - response.body.should.be == 'Hello World' + response.body.should.equal 'Hello World' # go in and play around with the cached metadata directly ... - cache.metastore.to_hash.values.length.should.be == 1 + cache.metastore.to_hash.values.length.should.equal 1 cache.metastore.to_hash.values.first.first[1]['Expires'] = Time.now.httpdate # build subsequent request; should be found but miss due to freshness get '/' app.should.be.called response.should.be.ok - response['Age'].to_i.should.be == 0 + response['Age'].to_i.should.equal 0 response.headers.should.include 'X-Content-Digest' cache.should.a.not.performed :hit cache.should.a.not.performed :miss cache.should.a.performed :fetch cache.should.a.performed :store - response.body.should.be == 'Hello World' + response.body.should.equal 'Hello World' end it 'validates cached responses with Last-Modified and no freshness information' do timestamp = Time.now.httpdate respond_with do |req,res| @@ -278,23 +382,23 @@ get '/' app.should.be.called response.should.be.ok response.headers.should.include 'Last-Modified' response.headers.should.include 'X-Content-Digest' - response.body.should.be == 'Hello World' + response.body.should.equal 'Hello World' cache.should.a.performed :miss cache.should.a.performed :store # build subsequent request; should be found but miss due to freshness get '/' app.should.be.called response.should.be.ok response.headers.should.include 'Last-Modified' response.headers.should.include 'X-Content-Digest' - response['Age'].to_i.should.be == 0 - response['X-Origin-Status'].should.be == '304' - response.body.should.be == 'Hello World' + response['Age'].to_i.should.equal 0 + response['X-Origin-Status'].should.equal '304' + response.body.should.equal 'Hello World' cache.should.a.not.performed :miss cache.should.a.performed :fetch cache.should.a.performed :store end @@ -312,23 +416,23 @@ get '/' app.should.be.called response.should.be.ok response.headers.should.include 'Etag' response.headers.should.include 'X-Content-Digest' - response.body.should.be == 'Hello World' + response.body.should.equal 'Hello World' cache.should.a.performed :miss cache.should.a.performed :store # build subsequent request; should be found but miss due to freshness get '/' app.should.be.called response.should.be.ok response.headers.should.include 'Etag' response.headers.should.include 'X-Content-Digest' - response['Age'].to_i.should.be == 0 - response['X-Origin-Status'].should.be == '304' - response.body.should.be == 'Hello World' + response['Age'].to_i.should.equal 0 + response['X-Origin-Status'].should.equal '304' + response.body.should.equal 'Hello World' cache.should.a.not.performed :miss cache.should.a.performed :fetch cache.should.a.performed :store end @@ -346,26 +450,62 @@ end end # first request should fetch from backend and store in cache get '/' - response.status.should.be == 200 - response.body.should.be == 'first response' + response.status.should.equal 200 + response.body.should.equal 'first response' # second request is validated, is invalid, and replaces cached entry get '/' - response.status.should.be == 200 - response.body.should.be == 'second response' + response.status.should.equal 200 + response.body.should.equal 'second response' # third respone is validated, valid, and returns cached entry get '/' - response.status.should.be == 200 - response.body.should.be == 'second response' + response.status.should.equal 200 + response.body.should.equal 'second response' - count.should.be == 3 + count.should.equal 3 end + it 'passes HEAD requests through directly on pass' do + respond_with do |req,res| + res.status = 200 + res.body = [] + req.request_method.should.equal 'HEAD' + end + + cache_config do + on(:receive) { pass! } + end + + head '/' + app.should.be.called + response.body.should.equal '' + end + + it 'uses cache to respond to HEAD requests when fresh' do + respond_with do |req,res| + res['Cache-Control'] = 'max-age=10' + res.body = ['Hello World'] + req.request_method.should.not.equal 'HEAD' + end + + get '/' + app.should.be.called + response.status.should.equal 200 + response.body.should.equal 'Hello World' + + head '/' + app.should.not.be.called + response.status.should.equal 200 + response.body.should.equal '' + response['Content-Length'].should.equal 'Hello World'.length.to_s + end + + describe 'with responses that include a Vary header' do before(:each) do count = 0 respond_with 200 do |req,res| res['Vary'] = 'Accept User-Agent Foo' @@ -378,59 +518,59 @@ it 'serves from cache when headers match' do get '/', 'HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/1.0' response.should.be.ok - response.body.should.be == 'Bob/1.0' + response.body.should.equal 'Bob/1.0' cache.should.a.performed :miss cache.should.a.performed :store get '/', 'HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/1.0' response.should.be.ok - response.body.should.be == 'Bob/1.0' + response.body.should.equal 'Bob/1.0' cache.should.a.performed :hit cache.should.a.not.performed :fetch response.headers.should.include 'X-Content-Digest' end it 'stores multiple responses when headers differ' do get '/', 'HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/1.0' response.should.be.ok - response.body.should.be == 'Bob/1.0' - response['X-Response-Count'].should.be == '1' + response.body.should.equal 'Bob/1.0' + response['X-Response-Count'].should.equal '1' get '/', 'HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/2.0' cache.should.a.performed :miss cache.should.a.performed :store - response.body.should.be == 'Bob/2.0' - response['X-Response-Count'].should.be == '2' + response.body.should.equal 'Bob/2.0' + response['X-Response-Count'].should.equal '2' get '/', 'HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/1.0' cache.should.a.performed :hit - response.body.should.be == 'Bob/1.0' - response['X-Response-Count'].should.be == '1' + response.body.should.equal 'Bob/1.0' + response['X-Response-Count'].should.equal '1' get '/', 'HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/2.0' cache.should.a.performed :hit - response.body.should.be == 'Bob/2.0' - response['X-Response-Count'].should.be == '2' + response.body.should.equal 'Bob/2.0' + response['X-Response-Count'].should.equal '2' get '/', 'HTTP_USER_AGENT' => 'Bob/2.0' cache.should.a.performed :miss - response.body.should.be == 'Bob/2.0' - response['X-Response-Count'].should.be == '3' + response.body.should.equal 'Bob/2.0' + response['X-Response-Count'].should.equal '3' end end describe 'when transitioning to the error state' do @@ -439,66 +579,66 @@ it 'creates a blank slate response object with 500 status with no args' do cache_config do on(:receive) { error! } end get '/' - response.status.should.be == 500 + response.status.should.equal 500 response.body.should.be.empty cache.should.a.performed :error end it 'sets the status code with one arg' do cache_config do on(:receive) { error! 505 } end get '/' - response.status.should.be == 505 + response.status.should.equal 505 end it 'sets the status and headers with args: status, Hash' do cache_config do on(:receive) { error! 504, 'Content-Type' => 'application/x-foo' } end get '/' - response.status.should.be == 504 - response['Content-Type'].should.be == 'application/x-foo' + response.status.should.equal 504 + response['Content-Type'].should.equal 'application/x-foo' response.body.should.be.empty end it 'sets the status and body with args: status, String' do cache_config do on(:receive) { error! 503, 'foo bar baz' } end get '/' - response.status.should.be == 503 - response.body.should.be == 'foo bar baz' + response.status.should.equal 503 + response.body.should.equal 'foo bar baz' end it 'sets the status and body with args: status, Array' do cache_config do on(:receive) { error! 503, ['foo bar baz'] } end get '/' - response.status.should.be == 503 - response.body.should.be == 'foo bar baz' + response.status.should.equal 503 + response.body.should.equal 'foo bar baz' end it 'fires the error event before finishing' do fired = false cache_config do on(:receive) { error! } on(:error) { fired = true - response.status.should.be == 500 + response.status.should.equal 500 response['Content-Type'] = 'application/x-foo' response.body = ['overridden response body'] } end get '/' fired.should.be true - response.status.should.be == 500 - response.body.should.be == 'overridden response body' - response['Content-Type'].should.be == 'application/x-foo' + response.status.should.equal 500 + response.body.should.equal 'overridden response body' + response['Content-Type'].should.equal 'application/x-foo' end end end