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.
- data/COPYING +18 -0
- data/README.rdoc +80 -0
- data/Rakefile +90 -0
- data/lib/rack/contrib.rb +40 -0
- data/lib/rack/contrib/accept_format.rb +46 -0
- data/lib/rack/contrib/access.rb +85 -0
- data/lib/rack/contrib/backstage.rb +20 -0
- data/lib/rack/contrib/bounce_favicon.rb +16 -0
- data/lib/rack/contrib/callbacks.rb +37 -0
- data/lib/rack/contrib/config.rb +16 -0
- data/lib/rack/contrib/cookies.rb +50 -0
- data/lib/rack/contrib/csshttprequest.rb +39 -0
- data/lib/rack/contrib/deflect.rb +137 -0
- data/lib/rack/contrib/evil.rb +12 -0
- data/lib/rack/contrib/garbagecollector.rb +14 -0
- data/lib/rack/contrib/jsonp.rb +41 -0
- data/lib/rack/contrib/lighttpd_script_name_fix.rb +16 -0
- data/lib/rack/contrib/locale.rb +31 -0
- data/lib/rack/contrib/mailexceptions.rb +120 -0
- data/lib/rack/contrib/nested_params.rb +143 -0
- data/lib/rack/contrib/not_found.rb +18 -0
- data/lib/rack/contrib/post_body_content_type_parser.rb +40 -0
- data/lib/rack/contrib/proctitle.rb +30 -0
- data/lib/rack/contrib/profiler.rb +108 -0
- data/lib/rack/contrib/relative_redirect.rb +44 -0
- data/lib/rack/contrib/response_cache.rb +59 -0
- data/lib/rack/contrib/route_exceptions.rb +49 -0
- data/lib/rack/contrib/sendfile.rb +142 -0
- data/lib/rack/contrib/signals.rb +63 -0
- data/lib/rack/contrib/time_zone.rb +25 -0
- data/rack-contrib.gemspec +88 -0
- data/test/404.html +1 -0
- data/test/Maintenance.html +1 -0
- data/test/mail_settings.rb +12 -0
- data/test/spec_rack_accept_format.rb +72 -0
- data/test/spec_rack_access.rb +154 -0
- data/test/spec_rack_backstage.rb +26 -0
- data/test/spec_rack_callbacks.rb +65 -0
- data/test/spec_rack_config.rb +22 -0
- data/test/spec_rack_contrib.rb +8 -0
- data/test/spec_rack_csshttprequest.rb +66 -0
- data/test/spec_rack_deflect.rb +107 -0
- data/test/spec_rack_evil.rb +19 -0
- data/test/spec_rack_garbagecollector.rb +13 -0
- data/test/spec_rack_jsonp.rb +34 -0
- data/test/spec_rack_lighttpd_script_name_fix.rb +16 -0
- data/test/spec_rack_mailexceptions.rb +97 -0
- data/test/spec_rack_nested_params.rb +46 -0
- data/test/spec_rack_not_found.rb +17 -0
- data/test/spec_rack_post_body_content_type_parser.rb +32 -0
- data/test/spec_rack_proctitle.rb +26 -0
- data/test/spec_rack_profiler.rb +41 -0
- data/test/spec_rack_relative_redirect.rb +78 -0
- data/test/spec_rack_response_cache.rb +137 -0
- data/test/spec_rack_sendfile.rb +86 -0
- 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
|