devver-rack-contrib 0.9.3

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.
Files changed (56) hide show
  1. data/COPYING +18 -0
  2. data/README.rdoc +80 -0
  3. data/Rakefile +90 -0
  4. data/lib/rack/contrib.rb +40 -0
  5. data/lib/rack/contrib/accept_format.rb +46 -0
  6. data/lib/rack/contrib/access.rb +85 -0
  7. data/lib/rack/contrib/backstage.rb +20 -0
  8. data/lib/rack/contrib/bounce_favicon.rb +16 -0
  9. data/lib/rack/contrib/callbacks.rb +37 -0
  10. data/lib/rack/contrib/config.rb +16 -0
  11. data/lib/rack/contrib/cookies.rb +50 -0
  12. data/lib/rack/contrib/csshttprequest.rb +39 -0
  13. data/lib/rack/contrib/deflect.rb +137 -0
  14. data/lib/rack/contrib/evil.rb +12 -0
  15. data/lib/rack/contrib/garbagecollector.rb +14 -0
  16. data/lib/rack/contrib/jsonp.rb +41 -0
  17. data/lib/rack/contrib/lighttpd_script_name_fix.rb +16 -0
  18. data/lib/rack/contrib/locale.rb +31 -0
  19. data/lib/rack/contrib/mailexceptions.rb +120 -0
  20. data/lib/rack/contrib/nested_params.rb +143 -0
  21. data/lib/rack/contrib/not_found.rb +18 -0
  22. data/lib/rack/contrib/post_body_content_type_parser.rb +40 -0
  23. data/lib/rack/contrib/proctitle.rb +30 -0
  24. data/lib/rack/contrib/profiler.rb +108 -0
  25. data/lib/rack/contrib/relative_redirect.rb +44 -0
  26. data/lib/rack/contrib/response_cache.rb +59 -0
  27. data/lib/rack/contrib/route_exceptions.rb +49 -0
  28. data/lib/rack/contrib/sendfile.rb +142 -0
  29. data/lib/rack/contrib/signals.rb +63 -0
  30. data/lib/rack/contrib/time_zone.rb +25 -0
  31. data/rack-contrib.gemspec +88 -0
  32. data/test/404.html +1 -0
  33. data/test/Maintenance.html +1 -0
  34. data/test/mail_settings.rb +12 -0
  35. data/test/spec_rack_accept_format.rb +72 -0
  36. data/test/spec_rack_access.rb +154 -0
  37. data/test/spec_rack_backstage.rb +26 -0
  38. data/test/spec_rack_callbacks.rb +65 -0
  39. data/test/spec_rack_config.rb +22 -0
  40. data/test/spec_rack_contrib.rb +8 -0
  41. data/test/spec_rack_csshttprequest.rb +66 -0
  42. data/test/spec_rack_deflect.rb +107 -0
  43. data/test/spec_rack_evil.rb +19 -0
  44. data/test/spec_rack_garbagecollector.rb +13 -0
  45. data/test/spec_rack_jsonp.rb +34 -0
  46. data/test/spec_rack_lighttpd_script_name_fix.rb +16 -0
  47. data/test/spec_rack_mailexceptions.rb +97 -0
  48. data/test/spec_rack_nested_params.rb +46 -0
  49. data/test/spec_rack_not_found.rb +17 -0
  50. data/test/spec_rack_post_body_content_type_parser.rb +32 -0
  51. data/test/spec_rack_proctitle.rb +26 -0
  52. data/test/spec_rack_profiler.rb +41 -0
  53. data/test/spec_rack_relative_redirect.rb +78 -0
  54. data/test/spec_rack_response_cache.rb +137 -0
  55. data/test/spec_rack_sendfile.rb +86 -0
  56. metadata +174 -0
@@ -0,0 +1,19 @@
1
+ require 'test/spec'
2
+ require 'rack/mock'
3
+ require 'rack/contrib/evil'
4
+ require 'erb'
5
+
6
+ context "Rack::Evil" do
7
+ app = lambda do |env|
8
+ template = ERB.new("<%= throw :response, [404, {'Content-Type' => 'text/html'}, 'Never know where it comes from'] %>")
9
+ [200, {'Content-Type' => 'text/plain'}, template.result(binding)]
10
+ end
11
+
12
+ specify "should enable the app to return the response from anywhere" do
13
+ status, headers, body = Rack::Evil.new(app).call({})
14
+
15
+ status.should.equal 404
16
+ headers['Content-Type'].should.equal 'text/html'
17
+ body.should.equal 'Never know where it comes from'
18
+ end
19
+ end
@@ -0,0 +1,13 @@
1
+ require 'test/spec'
2
+ require 'rack/mock'
3
+ require 'rack/contrib/garbagecollector'
4
+
5
+ context 'Rack::GarbageCollector' do
6
+
7
+ specify 'starts the garbage collector after each request' do
8
+ app = lambda { |env|
9
+ [200, {'Content-Type'=>'text/plain'}, ['Hello World']] }
10
+ Rack::GarbageCollector.new(app).call({})
11
+ end
12
+
13
+ end
@@ -0,0 +1,34 @@
1
+ require 'test/spec'
2
+ require 'rack/mock'
3
+ require 'rack/contrib/jsonp'
4
+
5
+ context "Rack::JSONP" do
6
+
7
+ context "when a callback parameter is provided" do
8
+ specify "should wrap the response body in the Javascript callback" do
9
+ test_body = '{"bar":"foo"}'
10
+ callback = 'foo'
11
+ app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, [test_body]] }
12
+ request = Rack::MockRequest.env_for("/", :input => "foo=bar&callback=#{callback}")
13
+ body = Rack::JSONP.new(app).call(request).last
14
+ body.should.equal "#{callback}(#{test_body})"
15
+ end
16
+
17
+ specify "should modify the content length to the correct value" do
18
+ test_body = '{"bar":"foo"}'
19
+ callback = 'foo'
20
+ app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, [test_body]] }
21
+ request = Rack::MockRequest.env_for("/", :input => "foo=bar&callback=#{callback}")
22
+ headers = Rack::JSONP.new(app).call(request)[1]
23
+ headers['Content-Length'].should.equal((test_body.length + callback.length + 2).to_s) # 2 parentheses
24
+ end
25
+ end
26
+
27
+ specify "should not change anything if no callback param is provided" do
28
+ app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['{"bar":"foo"}']] }
29
+ request = Rack::MockRequest.env_for("/", :input => "foo=bar")
30
+ body = Rack::JSONP.new(app).call(request).last
31
+ body.join.should.equal '{"bar":"foo"}'
32
+ end
33
+
34
+ end
@@ -0,0 +1,16 @@
1
+ require 'test/spec'
2
+ require 'rack/mock'
3
+ require 'rack/contrib/lighttpd_script_name_fix'
4
+
5
+ context "Rack::LighttpdScriptNameFix" do
6
+ specify "corrects SCRIPT_NAME and PATH_INFO set by lighttpd " do
7
+ env = {
8
+ "PATH_INFO" => "/foo/bar/baz",
9
+ "SCRIPT_NAME" => "/hello"
10
+ }
11
+ app = lambda { |_| [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
12
+ response = Rack::LighttpdScriptNameFix.new(app).call(env)
13
+ env['SCRIPT_NAME'].should.be.empty
14
+ env['PATH_INFO'].should.equal '/hello/foo/bar/baz'
15
+ end
16
+ end
@@ -0,0 +1,97 @@
1
+ require 'test/spec'
2
+ require 'rack/mock'
3
+
4
+ begin
5
+ require 'tmail'
6
+ require 'rack/contrib/mailexceptions'
7
+
8
+ require File.dirname(__FILE__) + '/mail_settings.rb'
9
+
10
+ class TestError < RuntimeError
11
+ end
12
+
13
+ def test_exception
14
+ raise TestError, 'Suffering Succotash!'
15
+ rescue => boom
16
+ return boom
17
+ end
18
+
19
+ context 'Rack::MailExceptions' do
20
+
21
+ setup do
22
+ @app = lambda { |env| raise TestError, 'Why, I say' }
23
+ @env = Rack::MockRequest.env_for("/foo",
24
+ 'FOO' => 'BAR',
25
+ :method => 'GET',
26
+ :input => 'THE BODY'
27
+ )
28
+ @smtp_settings = {
29
+ :server => 'example.com',
30
+ :domain => 'example.com',
31
+ :port => 500,
32
+ :authentication => :login,
33
+ :user_name => 'joe',
34
+ :password => 'secret'
35
+ }
36
+ end
37
+
38
+ specify 'yields a configuration object to the block when created' do
39
+ called = false
40
+ mailer =
41
+ Rack::MailExceptions.new(@app) do |mail|
42
+ called = true
43
+ mail.to 'foo@example.org'
44
+ mail.from 'bar@example.org'
45
+ mail.subject '[ERROR] %s'
46
+ mail.smtp @smtp_settings
47
+ end
48
+ called.should.be == true
49
+ end
50
+
51
+ specify 'generates a TMail object with configured settings' do
52
+ mailer =
53
+ Rack::MailExceptions.new(@app) do |mail|
54
+ mail.to 'foo@example.org'
55
+ mail.from 'bar@example.org'
56
+ mail.subject '[ERROR] %s'
57
+ mail.smtp @smtp_settings
58
+ end
59
+
60
+ tmail = mailer.send(:generate_mail, test_exception, @env)
61
+ tmail.to.should.equal ['foo@example.org']
62
+ tmail.from.should.equal ['bar@example.org']
63
+ tmail.subject.should.equal '[ERROR] Suffering Succotash!'
64
+ tmail.body.should.not.be.nil
65
+ tmail.body.should.be =~ /FOO:\s+"BAR"/
66
+ tmail.body.should.be =~ /^\s*THE BODY\s*$/
67
+ end
68
+
69
+ specify 'catches exceptions raised from app, sends mail, and re-raises' do
70
+ mailer =
71
+ Rack::MailExceptions.new(@app) do |mail|
72
+ mail.to 'foo@example.org'
73
+ mail.from 'bar@example.org'
74
+ mail.subject '[ERROR] %s'
75
+ mail.smtp @smtp_settings
76
+ end
77
+ lambda { mailer.call(@env) }.should.raise(TestError)
78
+ @env['mail.sent'].should.be == true
79
+ end
80
+
81
+ if TEST_SMTP && ! TEST_SMTP.empty?
82
+ specify 'sends mail' do
83
+ mailer =
84
+ Rack::MailExceptions.new(@app) do |mail|
85
+ mail.config.merge! TEST_SMTP
86
+ end
87
+ lambda { mailer.call(@env) }.should.raise(TestError)
88
+ @env['mail.sent'].should.be == true
89
+ end
90
+ else
91
+ STDERR.puts 'WARN: Skipping SMTP tests (edit test/mail_settings.rb to enable)'
92
+ end
93
+
94
+ end
95
+ rescue LoadError => boom
96
+ STDERR.puts "WARN: Skipping Rack::MailExceptions tests (tmail not installed)"
97
+ end
@@ -0,0 +1,46 @@
1
+ require 'test/spec'
2
+ require 'rack/mock'
3
+ require 'rack/contrib/nested_params'
4
+ require 'rack/methodoverride'
5
+
6
+ context Rack::NestedParams do
7
+
8
+ App = lambda { |env| [200, {'Content-Type' => 'text/plain'}, Rack::Request.new(env)] }
9
+
10
+ def env_for_post_with_headers(path, headers, body)
11
+ Rack::MockRequest.env_for(path, {:method => "POST", :input => body}.merge(headers))
12
+ end
13
+
14
+ def form_post(params, content_type = 'application/x-www-form-urlencoded')
15
+ params = Rack::Utils.build_query(params) if Hash === params
16
+ env_for_post_with_headers('/', {'CONTENT_TYPE' => content_type}, params)
17
+ end
18
+
19
+ def middleware
20
+ Rack::NestedParams.new(App)
21
+ end
22
+
23
+ specify "should handle requests with POST body Content-Type of application/x-www-form-urlencoded" do
24
+ req = middleware.call(form_post({'foo[bar][baz]' => 'nested'})).last
25
+ req.POST.should.equal({"foo" => { "bar" => { "baz" => "nested" }}})
26
+ end
27
+
28
+ specify "should not parse requests with other Content-Type" do
29
+ req = middleware.call(form_post({'foo[bar][baz]' => 'nested'}, 'text/plain')).last
30
+ req.POST.should.equal({})
31
+ end
32
+
33
+ specify "should work even after another middleware already parsed the request" do
34
+ app = Rack::MethodOverride.new(middleware)
35
+ req = app.call(form_post({'_method' => 'put', 'foo[bar]' => 'nested'})).last
36
+ req.POST.should.equal({'_method' => 'put', "foo" => { "bar" => "nested" }})
37
+ req.put?.should.equal true
38
+ end
39
+
40
+ specify "should make first boolean have precedence even after request already parsed" do
41
+ app = Rack::MethodOverride.new(middleware)
42
+ req = app.call(form_post("foo=1&foo=0")).last
43
+ req.POST.should.equal({"foo" => '1'})
44
+ end
45
+
46
+ end
@@ -0,0 +1,17 @@
1
+ require 'test/spec'
2
+ require 'rack/mock'
3
+ require 'rack/contrib/not_found'
4
+
5
+ context "Rack::NotFound" do
6
+
7
+ specify "should render the file at the given path for all requests" do
8
+ app = Rack::Builder.new do
9
+ use Rack::Lint
10
+ run Rack::NotFound.new('test/404.html')
11
+ end
12
+ response = Rack::MockRequest.new(app).get('/')
13
+ response.body.should.equal('Not Found')
14
+ response.status.should.equal(404)
15
+ end
16
+
17
+ end
@@ -0,0 +1,32 @@
1
+ require 'test/spec'
2
+ require 'rack/mock'
3
+
4
+ begin
5
+ require 'rack/contrib/post_body_content_type_parser'
6
+
7
+ context "Rack::PostBodyContentTypeParser" do
8
+
9
+ specify "should handle requests with POST body Content-Type of application/json" do
10
+ app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, Rack::Request.new(env).POST] }
11
+ env = env_for_post_with_headers('/', {'Content_Type'.upcase => 'application/json'}, {:body => "asdf", :status => "12"}.to_json)
12
+ body = Rack::PostBodyContentTypeParser.new(app).call(env).last
13
+ body['body'].should.equal "asdf"
14
+ body['status'].should.equal "12"
15
+ end
16
+
17
+ specify "should change nothing when the POST body content type isn't application/json" do
18
+ app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, Rack::Request.new(env).POST] }
19
+ body = app.call(Rack::MockRequest.env_for("/", :input => "body=asdf&status=12")).last
20
+ body['body'].should.equal "asdf"
21
+ body['status'].should.equal "12"
22
+ end
23
+
24
+ end
25
+
26
+ def env_for_post_with_headers(path, headers, body)
27
+ Rack::MockRequest.env_for(path, {:method => "POST", :input => body}.merge(headers))
28
+ end
29
+ rescue LoadError => e
30
+ # Missing dependency JSON, skipping tests.
31
+ STDERR.puts "WARN: Skipping Rack::PostBodyContentTypeParser tests (json not installed)"
32
+ end
@@ -0,0 +1,26 @@
1
+ require 'test/spec'
2
+ require 'rack/mock'
3
+ require 'rack/contrib/proctitle'
4
+
5
+ context "Rack::ProcTitle" do
6
+ F = ::File
7
+
8
+ progname = File.basename($0)
9
+ appname = F.expand_path(__FILE__).split('/')[-3]
10
+
11
+ def simple_app(body=['Hello World!'])
12
+ lambda { |env| [200, {'Content-Type' => 'text/plain'}, body] }
13
+ end
14
+
15
+ specify "should set the process title when created" do
16
+ Rack::ProcTitle.new(simple_app)
17
+ $0.should.equal "#{progname} [#{appname}] init ..."
18
+ end
19
+
20
+ specify "should set the process title on each request" do
21
+ app = Rack::ProcTitle.new(simple_app)
22
+ req = Rack::MockRequest.new(app)
23
+ 10.times { req.get('/hello') }
24
+ $0.should.equal "#{progname} [#{appname}/80] (10) GET /hello"
25
+ end
26
+ end
@@ -0,0 +1,41 @@
1
+ require 'test/spec'
2
+ require 'rack/mock'
3
+
4
+ begin
5
+ require 'rack/contrib/profiler'
6
+
7
+ context 'Rack::Profiler' do
8
+ app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, 'Oh hai der'] }
9
+ request = Rack::MockRequest.env_for("/", :input => "profile=process_time")
10
+
11
+ specify 'printer defaults to RubyProf::CallTreePrinter' do
12
+ profiler = Rack::Profiler.new(nil)
13
+ profiler.instance_variable_get('@printer').should.equal RubyProf::CallTreePrinter
14
+ profiler.instance_variable_get('@times').should.equal 1
15
+ end
16
+
17
+ specify 'CallTreePrinter has correct headers' do
18
+ headers = Rack::Profiler.new(app).call(request)[1]
19
+ headers.should.equal "Content-Disposition"=>"attachment; filename=\"/.process_time.tree\"", "Content-Type"=>"application/octet-stream"
20
+ end
21
+
22
+ specify 'FlatPrinter and GraphPrinter has Content-Type text/plain' do
23
+ %w(flat graph).each do |printer|
24
+ headers = Rack::Profiler.new(app, :printer => printer.to_sym).call(request)[1]
25
+ headers.should.equal "Content-Type"=>"text/plain"
26
+ end
27
+ end
28
+
29
+ specify 'GraphHtmlPrinter has Content-Type text/html' do
30
+ headers = Rack::Profiler.new(app, :printer => :graph_html).call(request)[1]
31
+ headers.should.equal "Content-Type"=>"text/html"
32
+ end
33
+ end
34
+
35
+ rescue LoadError => boom
36
+ if(boom.message=~/ruby-prof/)
37
+ $stderr.puts "WARN: Skipping Rack::Profiler tests (ruby-prof not installed)"
38
+ else
39
+ raise boom
40
+ end
41
+ end
@@ -0,0 +1,78 @@
1
+ require 'test/spec'
2
+ require 'rack/mock'
3
+ require 'rack/contrib/relative_redirect'
4
+ require 'fileutils'
5
+
6
+ context Rack::RelativeRedirect do
7
+ def request(opts={}, &block)
8
+ @def_status = opts[:status] if opts[:status]
9
+ @def_location = opts[:location] if opts[:location]
10
+ yield Rack::MockRequest.new(Rack::RelativeRedirect.new(@def_app, &opts[:block])).get(opts[:path]||@def_path, opts[:headers]||{})
11
+ end
12
+
13
+ setup do
14
+ @def_path = '/path/to/blah'
15
+ @def_status = 301
16
+ @def_location = '/redirect/to/blah'
17
+ @def_app = lambda { |env| [@def_status, {'Location' => @def_location}, [""]]}
18
+ end
19
+
20
+ specify "should make the location url an absolute url if currently a relative url" do
21
+ request do |r|
22
+ r.status.should.equal(301)
23
+ r.headers['Location'].should.equal('http://example.org/redirect/to/blah')
24
+ end
25
+ request(:status=>302, :location=>'/redirect') do |r|
26
+ r.status.should.equal(302)
27
+ r.headers['Location'].should.equal('http://example.org/redirect')
28
+ end
29
+ end
30
+
31
+ specify "should use the request path if the relative url is given and doesn't start with a slash" do
32
+ request(:status=>303, :location=>'redirect/to/blah') do |r|
33
+ r.status.should.equal(303)
34
+ r.headers['Location'].should.equal('http://example.org/path/to/redirect/to/blah')
35
+ end
36
+ request(:status=>303, :location=>'redirect') do |r|
37
+ r.status.should.equal(303)
38
+ r.headers['Location'].should.equal('http://example.org/path/to/redirect')
39
+ end
40
+ end
41
+
42
+ specify "should use a given block to make the url absolute" do
43
+ request(:block=>proc{|env, res| "https://example.org"}) do |r|
44
+ r.status.should.equal(301)
45
+ r.headers['Location'].should.equal('https://example.org/redirect/to/blah')
46
+ end
47
+ request(:status=>303, :location=>'/redirect', :block=>proc{|env, res| "https://e.org:9999/blah"}) do |r|
48
+ r.status.should.equal(303)
49
+ r.headers['Location'].should.equal('https://e.org:9999/blah/redirect')
50
+ end
51
+ end
52
+
53
+ specify "should not modify the location url unless the response is a redirect" do
54
+ status = 200
55
+ @def_app = lambda { |env| [status, {'Content-Type' => "text/html"}, [""]]}
56
+ request do |r|
57
+ r.status.should.equal(200)
58
+ r.headers.should.not.include?('Location')
59
+ end
60
+ status = 404
61
+ @def_app = lambda { |env| [status, {'Content-Type' => "text/html", 'Location' => 'redirect'}, [""]]}
62
+ request do |r|
63
+ r.status.should.equal(404)
64
+ r.headers['Location'].should.equal('redirect')
65
+ end
66
+ end
67
+
68
+ specify "should not modify the location url if it is already an absolute url" do
69
+ request(:location=>'https://example.org/') do |r|
70
+ r.status.should.equal(301)
71
+ r.headers['Location'].should.equal('https://example.org/')
72
+ end
73
+ request(:status=>302, :location=>'https://e.org:9999/redirect') do |r|
74
+ r.status.should.equal(302)
75
+ r.headers['Location'].should.equal('https://e.org:9999/redirect')
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,137 @@
1
+ require 'test/spec'
2
+ require 'rack/mock'
3
+ require 'rack/contrib/response_cache'
4
+ require 'fileutils'
5
+
6
+ context Rack::ResponseCache do
7
+ F = ::File
8
+
9
+ def request(opts={}, &block)
10
+ Rack::MockRequest.new(Rack::ResponseCache.new(block||@def_app, opts[:cache]||@cache, &opts[:rc_block])).send(opts[:meth]||:get, opts[:path]||@def_path, opts[:headers]||{})
11
+ end
12
+
13
+ setup do
14
+ @cache = {}
15
+ @def_disk_cache = F.join(F.dirname(__FILE__), 'response_cache_test_disk_cache')
16
+ @def_value = ["rack-response-cache"]
17
+ @def_path = '/path/to/blah'
18
+ @def_app = lambda { |env| [200, {'Content-Type' => env['CT'] || 'text/html'}, @def_value]}
19
+ end
20
+ teardown do
21
+ FileUtils.rm_rf(@def_disk_cache)
22
+ end
23
+
24
+ specify "should cache results to disk if cache is a string" do
25
+ request(:cache=>@def_disk_cache)
26
+ F.read(F.join(@def_disk_cache, 'path', 'to', 'blah.html')).should.equal @def_value.first
27
+ request(:path=>'/path/3', :cache=>@def_disk_cache)
28
+ F.read(F.join(@def_disk_cache, 'path', '3.html')).should.equal @def_value.first
29
+ end
30
+
31
+ specify "should cache results to given cache if cache is not a string" do
32
+ request
33
+ @cache.should.equal('/path/to/blah.html'=>@def_value)
34
+ request(:path=>'/path/3')
35
+ @cache.should.equal('/path/to/blah.html'=>@def_value, '/path/3.html'=>@def_value)
36
+ end
37
+
38
+ specify "should not CACHE RESults if request method is not GET" do
39
+ request(:meth=>:post)
40
+ @cache.should.equal({})
41
+ request(:meth=>:put)
42
+ @cache.should.equal({})
43
+ request(:meth=>:delete)
44
+ @cache.should.equal({})
45
+ end
46
+
47
+ specify "should not cache results if there is a query string" do
48
+ request(:path=>'/path/to/blah?id=1')
49
+ @cache.should.equal({})
50
+ request(:path=>'/path/to/?id=1')
51
+ @cache.should.equal({})
52
+ request(:path=>'/?id=1')
53
+ @cache.should.equal({})
54
+ end
55
+
56
+ specify "should cache results if there is an empty query string" do
57
+ request(:path=>'/?')
58
+ @cache.should.equal('/index.html'=>@def_value)
59
+ end
60
+
61
+ specify "should not cache results if the request is not sucessful (status 200)" do
62
+ request{|env| [404, {'Content-Type' => 'text/html'}, ['']]}
63
+ @cache.should.equal({})
64
+ request{|env| [500, {'Content-Type' => 'text/html'}, ['']]}
65
+ @cache.should.equal({})
66
+ request{|env| [302, {'Content-Type' => 'text/html'}, ['']]}
67
+ @cache.should.equal({})
68
+ end
69
+
70
+ specify "should not cache results if the block returns nil or false" do
71
+ request(:rc_block=>proc{false})
72
+ @cache.should.equal({})
73
+ request(:rc_block=>proc{nil})
74
+ @cache.should.equal({})
75
+ end
76
+
77
+ specify "should cache results to path returned by block" do
78
+ request(:rc_block=>proc{"1"})
79
+ @cache.should.equal("1"=>@def_value)
80
+ request(:rc_block=>proc{"2"})
81
+ @cache.should.equal("1"=>@def_value, "2"=>@def_value)
82
+ end
83
+
84
+ specify "should pass the environment and response to the block" do
85
+ e, r = nil, nil
86
+ request(:rc_block=>proc{|env,res| e, r = env, res; nil})
87
+ e['PATH_INFO'].should.equal @def_path
88
+ e['REQUEST_METHOD'].should.equal 'GET'
89
+ e['QUERY_STRING'].should.equal ''
90
+ r.should.equal([200, {"Content-Type"=>"text/html"}, ["rack-response-cache"]])
91
+ end
92
+
93
+ specify "should unescape the path by default" do
94
+ request(:path=>'/path%20with%20spaces')
95
+ @cache.should.equal('/path with spaces.html'=>@def_value)
96
+ request(:path=>'/path%3chref%3e')
97
+ @cache.should.equal('/path with spaces.html'=>@def_value, '/path<href>.html'=>@def_value)
98
+ end
99
+
100
+ specify "should cache html, css, and xml responses by default" do
101
+ request(:path=>'/a')
102
+ @cache.should.equal('/a.html'=>@def_value)
103
+ request(:path=>'/b', :headers=>{'CT'=>'text/xml'})
104
+ @cache.should.equal('/a.html'=>@def_value, '/b.xml'=>@def_value)
105
+ request(:path=>'/c', :headers=>{'CT'=>'text/css'})
106
+ @cache.should.equal('/a.html'=>@def_value, '/b.xml'=>@def_value, '/c.css'=>@def_value)
107
+ end
108
+
109
+ specify "should cache responses by default with the extension added if not already present" do
110
+ request(:path=>'/a.html')
111
+ @cache.should.equal('/a.html'=>@def_value)
112
+ request(:path=>'/b.xml', :headers=>{'CT'=>'text/xml'})
113
+ @cache.should.equal('/a.html'=>@def_value, '/b.xml'=>@def_value)
114
+ request(:path=>'/c.css', :headers=>{'CT'=>'text/css'})
115
+ @cache.should.equal('/a.html'=>@def_value, '/b.xml'=>@def_value, '/c.css'=>@def_value)
116
+ end
117
+
118
+ specify "should not delete existing extensions" do
119
+ request(:path=>'/d.css', :headers=>{'CT'=>'text/html'})
120
+ @cache.should.equal('/d.css.html'=>@def_value)
121
+ end
122
+
123
+ specify "should cache html responses with empty basename to index.html by default" do
124
+ request(:path=>'/')
125
+ @cache.should.equal('/index.html'=>@def_value)
126
+ request(:path=>'/blah/')
127
+ @cache.should.equal('/index.html'=>@def_value, '/blah/index.html'=>@def_value)
128
+ request(:path=>'/blah/2/')
129
+ @cache.should.equal('/index.html'=>@def_value, '/blah/index.html'=>@def_value, '/blah/2/index.html'=>@def_value)
130
+ end
131
+
132
+ specify "should raise an error if a cache argument is not provided" do
133
+ app = Rack::Builder.new{use Rack::ResponseCache; run lambda { |env| [200, {'Content-Type' => 'text/plain'}, Rack::Request.new(env).POST]}}
134
+ proc{Rack::MockRequest.new(app).get('/')}.should.raise(ArgumentError)
135
+ end
136
+
137
+ end