rack 1.2.8 → 1.3.0.beta

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rack might be problematic. Click here for more details.

Files changed (89) hide show
  1. data/README +9 -177
  2. data/Rakefile +2 -1
  3. data/SPEC +2 -2
  4. data/lib/rack.rb +2 -13
  5. data/lib/rack/auth/abstract/request.rb +7 -5
  6. data/lib/rack/auth/digest/md5.rb +6 -2
  7. data/lib/rack/auth/digest/params.rb +5 -7
  8. data/lib/rack/auth/digest/request.rb +1 -1
  9. data/lib/rack/backports/uri/common.rb +64 -0
  10. data/lib/rack/builder.rb +60 -3
  11. data/lib/rack/chunked.rb +29 -22
  12. data/lib/rack/conditionalget.rb +35 -16
  13. data/lib/rack/content_length.rb +3 -3
  14. data/lib/rack/deflater.rb +5 -2
  15. data/lib/rack/etag.rb +38 -10
  16. data/lib/rack/file.rb +76 -43
  17. data/lib/rack/handler.rb +13 -7
  18. data/lib/rack/handler/cgi.rb +0 -2
  19. data/lib/rack/handler/fastcgi.rb +13 -4
  20. data/lib/rack/handler/lsws.rb +0 -2
  21. data/lib/rack/handler/mongrel.rb +12 -2
  22. data/lib/rack/handler/scgi.rb +9 -1
  23. data/lib/rack/handler/thin.rb +7 -1
  24. data/lib/rack/handler/webrick.rb +12 -5
  25. data/lib/rack/lint.rb +2 -2
  26. data/lib/rack/lock.rb +29 -3
  27. data/lib/rack/methodoverride.rb +1 -1
  28. data/lib/rack/mime.rb +2 -2
  29. data/lib/rack/mock.rb +28 -33
  30. data/lib/rack/multipart.rb +34 -0
  31. data/lib/rack/multipart/generator.rb +93 -0
  32. data/lib/rack/multipart/parser.rb +164 -0
  33. data/lib/rack/multipart/uploaded_file.rb +30 -0
  34. data/lib/rack/request.rb +55 -19
  35. data/lib/rack/response.rb +10 -8
  36. data/lib/rack/sendfile.rb +14 -18
  37. data/lib/rack/server.rb +55 -8
  38. data/lib/rack/session/abstract/id.rb +233 -22
  39. data/lib/rack/session/cookie.rb +99 -46
  40. data/lib/rack/session/memcache.rb +30 -56
  41. data/lib/rack/session/pool.rb +22 -43
  42. data/lib/rack/showexceptions.rb +40 -11
  43. data/lib/rack/showstatus.rb +9 -2
  44. data/lib/rack/static.rb +29 -9
  45. data/lib/rack/urlmap.rb +6 -1
  46. data/lib/rack/utils.rb +67 -326
  47. data/rack.gemspec +2 -3
  48. data/test/builder/anything.rb +5 -0
  49. data/test/builder/comment.ru +4 -0
  50. data/test/builder/end.ru +3 -0
  51. data/test/builder/options.ru +2 -0
  52. data/test/cgi/lighttpd.conf +1 -1
  53. data/test/cgi/lighttpd.errors +412 -0
  54. data/test/multipart/content_type_and_no_filename +6 -0
  55. data/test/multipart/text +5 -0
  56. data/test/multipart/webkit +32 -0
  57. data/test/registering_handler/rack/handler/registering_myself.rb +8 -0
  58. data/test/spec_auth_digest.rb +20 -5
  59. data/test/spec_builder.rb +29 -0
  60. data/test/spec_cgi.rb +11 -0
  61. data/test/spec_chunked.rb +1 -1
  62. data/test/spec_commonlogger.rb +1 -1
  63. data/test/spec_conditionalget.rb +47 -0
  64. data/test/spec_content_length.rb +0 -6
  65. data/test/spec_content_type.rb +5 -5
  66. data/test/spec_deflater.rb +46 -2
  67. data/test/spec_etag.rb +68 -1
  68. data/test/spec_fastcgi.rb +11 -0
  69. data/test/spec_file.rb +54 -3
  70. data/test/spec_handler.rb +23 -5
  71. data/test/spec_lint.rb +2 -2
  72. data/test/spec_lock.rb +111 -5
  73. data/test/spec_methodoverride.rb +2 -2
  74. data/test/spec_mock.rb +3 -3
  75. data/test/spec_mongrel.rb +1 -2
  76. data/test/spec_multipart.rb +279 -0
  77. data/test/spec_request.rb +222 -38
  78. data/test/spec_response.rb +9 -3
  79. data/test/spec_server.rb +74 -0
  80. data/test/spec_session_abstract_id.rb +43 -0
  81. data/test/spec_session_cookie.rb +97 -15
  82. data/test/spec_session_memcache.rb +60 -50
  83. data/test/spec_session_pool.rb +63 -40
  84. data/test/spec_showexceptions.rb +64 -0
  85. data/test/spec_static.rb +23 -0
  86. data/test/spec_utils.rb +65 -351
  87. data/test/spec_webrick.rb +23 -4
  88. metadata +35 -15
  89. data/test/spec_auth.rb +0 -57
@@ -0,0 +1,6 @@
1
+ --AaB03x
2
+ Content-Disposition: form-data; name="text"
3
+ Content-Type: text/plain; charset=US-ASCII
4
+
5
+ contents
6
+ --AaB03x--
@@ -3,6 +3,11 @@ Content-Disposition: form-data; name="submit-name"
3
3
 
4
4
  Larry
5
5
  --AaB03x
6
+ Content-Disposition: form-data; name="submit-name-with-content"
7
+ Content-Type: text/plain
8
+
9
+ Berry
10
+ --AaB03x
6
11
  Content-Disposition: form-data; name="files"; filename="file1.txt"
7
12
  Content-Type: text/plain
8
13
 
@@ -0,0 +1,32 @@
1
+ ------WebKitFormBoundaryWLHCs9qmcJJoyjKR
2
+ Content-Disposition: form-data; name="_method"
3
+
4
+ put
5
+ ------WebKitFormBoundaryWLHCs9qmcJJoyjKR
6
+ Content-Disposition: form-data; name="profile[blog]"
7
+
8
+
9
+ ------WebKitFormBoundaryWLHCs9qmcJJoyjKR
10
+ Content-Disposition: form-data; name="profile[public_email]"
11
+
12
+
13
+ ------WebKitFormBoundaryWLHCs9qmcJJoyjKR
14
+ Content-Disposition: form-data; name="profile[interests]"
15
+
16
+
17
+ ------WebKitFormBoundaryWLHCs9qmcJJoyjKR
18
+ Content-Disposition: form-data; name="profile[bio]"
19
+
20
+ hello
21
+
22
+ "quote"
23
+ ------WebKitFormBoundaryWLHCs9qmcJJoyjKR
24
+ Content-Disposition: form-data; name="media"; filename=""
25
+ Content-Type: application/octet-stream
26
+
27
+
28
+ ------WebKitFormBoundaryWLHCs9qmcJJoyjKR
29
+ Content-Disposition: form-data; name="commit"
30
+
31
+ Save
32
+ ------WebKitFormBoundaryWLHCs9qmcJJoyjKR--
@@ -0,0 +1,8 @@
1
+ module Rack
2
+ module Handler
3
+ class RegisteringMyself
4
+ end
5
+
6
+ register :registering_myself, RegisteringMyself
7
+ end
8
+ end
@@ -8,17 +8,15 @@ describe Rack::Auth::Digest::MD5 do
8
8
 
9
9
  def unprotected_app
10
10
  lambda do |env|
11
- [ 200, {'Content-Type' => 'text/plain'}, ["Hi #{env['REMOTE_USER']}"] ]
11
+ friend = Rack::Utils.parse_query(env["QUERY_STRING"])["friend"]
12
+ [ 200, {'Content-Type' => 'text/plain'}, ["Hi #{env['REMOTE_USER']}#{friend ? " and #{friend}" : ''}"] ]
12
13
  end
13
14
  end
14
15
 
15
16
  def protected_app
16
- app = Rack::Auth::Digest::MD5.new(unprotected_app) do |username|
17
+ Rack::Auth::Digest::MD5.new(unprotected_app, :realm => realm, :opaque => 'this-should-be-secret') do |username|
17
18
  { 'Alice' => 'correct-password' }[username]
18
19
  end
19
- app.realm = realm
20
- app.opaque = 'this-should-be-secret'
21
- app
22
20
  end
23
21
 
24
22
  def protected_app_with_hashed_passwords
@@ -207,6 +205,23 @@ describe Rack::Auth::Digest::MD5 do
207
205
  end
208
206
  end
209
207
 
208
+ should 'return application output when used with a query string and path as uri' do
209
+ @request = Rack::MockRequest.new(partially_protected_app)
210
+ request_with_digest_auth 'GET', '/protected?friend=Mike', 'Alice', 'correct-password' do |response|
211
+ response.status.should.equal 200
212
+ response.body.to_s.should.equal 'Hi Alice and Mike'
213
+ end
214
+ end
215
+
216
+ should 'return application output when used with a query string and fullpath as uri' do
217
+ @request = Rack::MockRequest.new(partially_protected_app)
218
+ qs_uri = '/protected?friend=Mike'
219
+ request_with_digest_auth 'GET', qs_uri, 'Alice', 'correct-password', 'uri' => qs_uri do |response|
220
+ response.status.should.equal 200
221
+ response.body.to_s.should.equal 'Hi Alice and Mike'
222
+ end
223
+ end
224
+
210
225
  should 'return application output if correct credentials given for POST' do
211
226
  request_with_digest_auth 'POST', '/', 'Alice', 'correct-password' do |response|
212
227
  response.status.should.equal 200
@@ -120,4 +120,33 @@ describe Rack::Builder do
120
120
  Rack::MockRequest.new(app).get("/").should.be.server_error
121
121
  end
122
122
 
123
+ describe "parse_file" do
124
+ def config_file(name)
125
+ File.join(File.dirname(__FILE__), 'builder', name)
126
+ end
127
+
128
+ it "parses commented options" do
129
+ app, options = Rack::Builder.parse_file config_file('options.ru')
130
+ options[:debug].should.be.true
131
+ Rack::MockRequest.new(app).get("/").body.to_s.should.equal 'OK'
132
+ end
133
+
134
+ it "removes __END__ before evaluating app" do
135
+ app, options = Rack::Builder.parse_file config_file('end.ru')
136
+ Rack::MockRequest.new(app).get("/").body.to_s.should.equal 'OK'
137
+ end
138
+
139
+ it "supports multi-line comments" do
140
+ lambda {
141
+ Rack::Builder.parse_file config_file('comment.ru')
142
+ }.should.not.raise(SyntaxError)
143
+ end
144
+
145
+ it "requires anything not ending in .ru" do
146
+ $: << File.dirname(__FILE__)
147
+ app, options = Rack::Builder.parse_file 'builder/anything'
148
+ Rack::MockRequest.new(app).get("/").body.to_s.should.equal 'OK'
149
+ $:.pop
150
+ end
151
+ end
123
152
  end
@@ -1,3 +1,4 @@
1
+ begin
1
2
  require File.expand_path('../testrequest', __FILE__)
2
3
  require 'rack/handler/cgi'
3
4
 
@@ -7,6 +8,10 @@ describe Rack::Handler::CGI do
7
8
  @host = '0.0.0.0'
8
9
  @port = 9203
9
10
 
11
+ if `which lighttpd` && !$?.success?
12
+ raise "lighttpd not found"
13
+ end
14
+
10
15
  # Keep this first.
11
16
  $pid = fork {
12
17
  ENV['RACK_ENV'] = 'deployment'
@@ -89,3 +94,9 @@ describe Rack::Handler::CGI do
89
94
  Process.wait($pid).should == $pid
90
95
  end
91
96
  end
97
+
98
+ rescue RuntimeError
99
+ $stderr.puts "Skipping Rack::Handler::CGI tests (lighttpd is required). Install lighttpd and try again."
100
+ rescue NotImplementedError
101
+ $stderr.puts "Your Ruby implemenation or platform does not support fork. Skipping Rack::Handler::CGI tests."
102
+ end
@@ -52,7 +52,7 @@ describe Rack::Chunked do
52
52
  [100, 204, 304].each do |status_code|
53
53
  should "not modify response when status code is #{status_code}" do
54
54
  app = lambda { |env| [status_code, {}, []] }
55
- status, headers, body = Rack::Chunked.new(app).call(@env)
55
+ status, headers, _ = Rack::Chunked.new(app).call(@env)
56
56
  status.should.equal status_code
57
57
  headers.should.not.include 'Transfer-Encoding'
58
58
  end
@@ -27,7 +27,7 @@ describe Rack::CommonLogger do
27
27
 
28
28
  should "log to anything with +write+" do
29
29
  log = StringIO.new
30
- res = Rack::MockRequest.new(Rack::CommonLogger.new(app, log)).get("/")
30
+ Rack::MockRequest.new(Rack::CommonLogger.new(app, log)).get("/")
31
31
 
32
32
  log.string.should =~ /"GET \/ " 200 #{length} /
33
33
  end
@@ -15,6 +15,17 @@ describe Rack::ConditionalGet do
15
15
  response.body.should.be.empty
16
16
  end
17
17
 
18
+ should "set a 304 status and truncate body when If-Modified-Since hits and is higher than current time" do
19
+ app = Rack::ConditionalGet.new(lambda { |env|
20
+ [200, {'Last-Modified'=>(Time.now - 3600).httpdate}, ['TEST']] })
21
+
22
+ response = Rack::MockRequest.new(app).
23
+ get("/", 'HTTP_IF_MODIFIED_SINCE' => Time.now.httpdate)
24
+
25
+ response.status.should.equal 304
26
+ response.body.should.be.empty
27
+ end
28
+
18
29
  should "set a 304 status and truncate body when If-None-Match hits" do
19
30
  app = Rack::ConditionalGet.new(lambda { |env|
20
31
  [200, {'Etag'=>'1234'}, ['TEST']] })
@@ -26,6 +37,30 @@ describe Rack::ConditionalGet do
26
37
  response.body.should.be.empty
27
38
  end
28
39
 
40
+ should "not set a 304 status if If-Modified-Since hits but Etag does not" do
41
+ timestamp = Time.now.httpdate
42
+ app = Rack::ConditionalGet.new(lambda { |env|
43
+ [200, {'Last-Modified'=>timestamp, 'Etag'=>'1234'}, ['TEST']] })
44
+
45
+ response = Rack::MockRequest.new(app).
46
+ get("/", 'HTTP_IF_MODIFIED_SINCE' => timestamp, 'HTTP_IF_NONE_MATCH' => '4321')
47
+
48
+ response.status.should.equal 200
49
+ response.body.should.equal 'TEST'
50
+ end
51
+
52
+ should "set a 304 status and truncate body when both If-None-Match and If-Modified-Since hits" do
53
+ timestamp = Time.now.httpdate
54
+ app = Rack::ConditionalGet.new(lambda { |env|
55
+ [200, {'Last-Modified'=>timestamp, 'Etag'=>'1234'}, ['TEST']] })
56
+
57
+ response = Rack::MockRequest.new(app).
58
+ get("/", 'HTTP_IF_MODIFIED_SINCE' => timestamp, 'HTTP_IF_NONE_MATCH' => '1234')
59
+
60
+ response.status.should.equal 304
61
+ response.body.should.be.empty
62
+ end
63
+
29
64
  should "not affect non-GET/HEAD requests" do
30
65
  app = Rack::ConditionalGet.new(lambda { |env|
31
66
  [200, {'Etag'=>'1234'}, ['TEST']] })
@@ -36,4 +71,16 @@ describe Rack::ConditionalGet do
36
71
  response.status.should.equal 200
37
72
  response.body.should.equal 'TEST'
38
73
  end
74
+
75
+ should "not affect non-200 requests" do
76
+ app = Rack::ConditionalGet.new(lambda { |env|
77
+ [302, {'Etag'=>'1234'}, ['TEST']] })
78
+
79
+ response = Rack::MockRequest.new(app).
80
+ get("/", 'HTTP_IF_NONE_MATCH' => '1234')
81
+
82
+ response.status.should.equal 302
83
+ response.body.should.equal 'TEST'
84
+ end
85
+
39
86
  end
@@ -1,12 +1,6 @@
1
1
  require 'rack/content_length'
2
2
 
3
3
  describe Rack::ContentLength do
4
- should "set Content-Length on String bodies if none is set" do
5
- app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, "Hello, World!"] }
6
- response = Rack::ContentLength.new(app).call({})
7
- response[1]['Content-Length'].should.equal '13'
8
- end
9
-
10
4
  should "set Content-Length on Array bodies if none is set" do
11
5
  app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
12
6
  response = Rack::ContentLength.new(app).call({})
@@ -3,26 +3,26 @@ require 'rack/content_type'
3
3
  describe Rack::ContentType do
4
4
  should "set Content-Type to default text/html if none is set" do
5
5
  app = lambda { |env| [200, {}, "Hello, World!"] }
6
- status, headers, body = Rack::ContentType.new(app).call({})
6
+ headers = Rack::ContentType.new(app).call({})[1]
7
7
  headers['Content-Type'].should.equal 'text/html'
8
8
  end
9
9
 
10
10
  should "set Content-Type to chosen default if none is set" do
11
11
  app = lambda { |env| [200, {}, "Hello, World!"] }
12
- status, headers, body =
13
- Rack::ContentType.new(app, 'application/octet-stream').call({})
12
+ headers =
13
+ Rack::ContentType.new(app, 'application/octet-stream').call({})[1]
14
14
  headers['Content-Type'].should.equal 'application/octet-stream'
15
15
  end
16
16
 
17
17
  should "not change Content-Type if it is already set" do
18
18
  app = lambda { |env| [200, {'Content-Type' => 'foo/bar'}, "Hello, World!"] }
19
- status, headers, body = Rack::ContentType.new(app).call({})
19
+ headers = Rack::ContentType.new(app).call({})[1]
20
20
  headers['Content-Type'].should.equal 'foo/bar'
21
21
  end
22
22
 
23
23
  should "detect Content-Type case insensitive" do
24
24
  app = lambda { |env| [200, {'CONTENT-Type' => 'foo/bar'}, "Hello, World!"] }
25
- status, headers, body = Rack::ContentType.new(app).call({})
25
+ headers = Rack::ContentType.new(app).call({})[1]
26
26
  headers.to_a.select { |k,v| k.downcase == "content-type" }.
27
27
  should.equal [["CONTENT-Type","foo/bar"]]
28
28
  end
@@ -2,6 +2,7 @@ require 'stringio'
2
2
  require 'time' # for Time#httpdate
3
3
  require 'rack/deflater'
4
4
  require 'rack/mock'
5
+ require 'zlib'
5
6
 
6
7
  describe Rack::Deflater do
7
8
  def build_response(status, body, accept_encoding, headers = {})
@@ -13,6 +14,11 @@ describe Rack::Deflater do
13
14
  return response
14
15
  end
15
16
 
17
+ def inflate(buf)
18
+ inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS)
19
+ inflater.inflate(buf) << inflater.finish
20
+ end
21
+
16
22
  should "be able to deflate bodies that respond to each" do
17
23
  body = Object.new
18
24
  class << body; def each; yield("foo"); yield("bar"); end; end
@@ -26,7 +32,26 @@ describe Rack::Deflater do
26
32
  })
27
33
  buf = ''
28
34
  response[2].each { |part| buf << part }
29
- buf.should.equal("K\313\317OJ,\002\000")
35
+ inflate(buf).should.equal("foobar")
36
+ end
37
+
38
+ should "flush deflated chunks to the client as they become ready" do
39
+ body = Object.new
40
+ class << body; def each; yield("foo"); yield("bar"); end; end
41
+
42
+ response = build_response(200, body, "deflate")
43
+
44
+ response[0].should.equal(200)
45
+ response[1].should.equal({
46
+ "Content-Encoding" => "deflate",
47
+ "Vary" => "Accept-Encoding"
48
+ })
49
+ buf = []
50
+ inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS)
51
+ response[2].each { |part| buf << inflater.inflate(part) }
52
+ buf << inflater.finish
53
+ buf.delete_if { |part| part.empty? }
54
+ buf.should.equal(%w(foo bar))
30
55
  end
31
56
 
32
57
  # TODO: This is really just a special case of the above...
@@ -40,7 +65,7 @@ describe Rack::Deflater do
40
65
  })
41
66
  buf = ''
42
67
  response[2].each { |part| buf << part }
43
- buf.should.equal("\363H\315\311\311W(\317/\312IQ\004\000")
68
+ inflate(buf).should.equal("Hello world!")
44
69
  end
45
70
 
46
71
  should "be able to gzip bodies that respond to each" do
@@ -63,6 +88,25 @@ describe Rack::Deflater do
63
88
  gz.close
64
89
  end
65
90
 
91
+ should "flush gzipped chunks to the client as they become ready" do
92
+ body = Object.new
93
+ class << body; def each; yield("foo"); yield("bar"); end; end
94
+
95
+ response = build_response(200, body, "gzip")
96
+
97
+ response[0].should.equal(200)
98
+ response[1].should.equal({
99
+ "Content-Encoding" => "gzip",
100
+ "Vary" => "Accept-Encoding"
101
+ })
102
+ buf = []
103
+ inflater = Zlib::Inflate.new(Zlib::MAX_WBITS + 32)
104
+ response[2].each { |part| buf << inflater.inflate(part) }
105
+ buf << inflater.finish
106
+ buf.delete_if { |part| part.empty? }
107
+ buf.should.equal(%w(foo bar))
108
+ end
109
+
66
110
  should "be able to fallback to no deflation" do
67
111
  response = build_response(200, "Hello world!", "superzip")
68
112
 
@@ -1,15 +1,82 @@
1
1
  require 'rack/etag'
2
+ require 'time'
2
3
 
3
4
  describe Rack::ETag do
4
- should "set ETag if none is set" do
5
+ def sendfile_body
6
+ res = ['Hello World']
7
+ def res.to_path ; "/tmp/hello.txt" ; end
8
+ res
9
+ end
10
+
11
+ should "set ETag if none is set if status is 200" do
5
12
  app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
6
13
  response = Rack::ETag.new(app).call({})
7
14
  response[1]['ETag'].should.equal "\"65a8e27d8879283831b664bd8b7f0ad4\""
8
15
  end
9
16
 
17
+ should "set ETag if none is set if status is 201" do
18
+ app = lambda { |env| [201, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
19
+ response = Rack::ETag.new(app).call({})
20
+ response[1]['ETag'].should.equal "\"65a8e27d8879283831b664bd8b7f0ad4\""
21
+ end
22
+
23
+ should "set Cache-Control to 'max-age=0, private, must-revalidate' (default) if none is set" do
24
+ app = lambda { |env| [201, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
25
+ response = Rack::ETag.new(app).call({})
26
+ response[1]['Cache-Control'].should.equal 'max-age=0, private, must-revalidate'
27
+ end
28
+
29
+ should "set Cache-Control to chosen one if none is set" do
30
+ app = lambda { |env| [201, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
31
+ response = Rack::ETag.new(app, nil, 'public').call({})
32
+ response[1]['Cache-Control'].should.equal 'public'
33
+ end
34
+
35
+ should "set a given Cache-Control even if digest could not be calculated" do
36
+ app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, []] }
37
+ response = Rack::ETag.new(app, 'no-cache').call({})
38
+ response[1]['Cache-Control'].should.equal 'no-cache'
39
+ end
40
+
41
+ should "not set Cache-Control if it is already set" do
42
+ app = lambda { |env| [201, {'Content-Type' => 'text/plain', 'Cache-Control' => 'public'}, ["Hello, World!"]] }
43
+ response = Rack::ETag.new(app).call({})
44
+ response[1]['Cache-Control'].should.equal 'public'
45
+ end
46
+
10
47
  should "not change ETag if it is already set" do
11
48
  app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'ETag' => '"abc"'}, ["Hello, World!"]] }
12
49
  response = Rack::ETag.new(app).call({})
13
50
  response[1]['ETag'].should.equal "\"abc\""
14
51
  end
52
+
53
+ should "not set ETag if body is empty" do
54
+ app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'Last-Modified' => Time.now.httpdate}, []] }
55
+ response = Rack::ETag.new(app).call({})
56
+ response[1]['ETag'].should.be.nil
57
+ end
58
+
59
+ should "not set ETag if Last-Modified is set" do
60
+ app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'Last-Modified' => Time.now.httpdate}, ["Hello, World!"]] }
61
+ response = Rack::ETag.new(app).call({})
62
+ response[1]['ETag'].should.be.nil
63
+ end
64
+
65
+ should "not set ETag if a sendfile_body is given" do
66
+ app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, sendfile_body] }
67
+ response = Rack::ETag.new(app).call({})
68
+ response[1]['ETag'].should.be.nil
69
+ end
70
+
71
+ should "not set ETag if a status is not 200 or 201" do
72
+ app = lambda { |env| [401, {'Content-Type' => 'text/plain'}, ['Access denied.']] }
73
+ response = Rack::ETag.new(app).call({})
74
+ response[1]['ETag'].should.be.nil
75
+ end
76
+
77
+ should "not set ETag if no-cache is given" do
78
+ app = lambda { |env| [200, {'Cache-Control' => 'no-cache'}, ['Hello, World!']] }
79
+ response = Rack::ETag.new(app).call({})
80
+ response[1]['ETag'].should.be.nil
81
+ end
15
82
  end