rack 0.9.1 → 1.0.0

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 (79) hide show
  1. data/COPYING +1 -1
  2. data/RDOX +115 -16
  3. data/README +54 -7
  4. data/Rakefile +61 -85
  5. data/SPEC +50 -17
  6. data/bin/rackup +9 -5
  7. data/example/protectedlobster.ru +1 -1
  8. data/lib/rack.rb +7 -3
  9. data/lib/rack/auth/abstract/handler.rb +13 -4
  10. data/lib/rack/auth/digest/md5.rb +1 -1
  11. data/lib/rack/auth/digest/request.rb +2 -2
  12. data/lib/rack/auth/openid.rb +344 -302
  13. data/lib/rack/builder.rb +1 -5
  14. data/lib/rack/chunked.rb +49 -0
  15. data/lib/rack/conditionalget.rb +4 -0
  16. data/lib/rack/content_length.rb +7 -3
  17. data/lib/rack/content_type.rb +23 -0
  18. data/lib/rack/deflater.rb +83 -74
  19. data/lib/rack/directory.rb +5 -2
  20. data/lib/rack/file.rb +4 -1
  21. data/lib/rack/handler.rb +22 -1
  22. data/lib/rack/handler/cgi.rb +7 -3
  23. data/lib/rack/handler/fastcgi.rb +26 -24
  24. data/lib/rack/handler/lsws.rb +7 -4
  25. data/lib/rack/handler/mongrel.rb +5 -3
  26. data/lib/rack/handler/scgi.rb +5 -3
  27. data/lib/rack/handler/thin.rb +3 -0
  28. data/lib/rack/handler/webrick.rb +11 -5
  29. data/lib/rack/lint.rb +138 -66
  30. data/lib/rack/lock.rb +16 -0
  31. data/lib/rack/mime.rb +4 -4
  32. data/lib/rack/mock.rb +3 -3
  33. data/lib/rack/reloader.rb +88 -46
  34. data/lib/rack/request.rb +46 -10
  35. data/lib/rack/response.rb +15 -3
  36. data/lib/rack/rewindable_input.rb +98 -0
  37. data/lib/rack/session/abstract/id.rb +71 -82
  38. data/lib/rack/session/cookie.rb +2 -0
  39. data/lib/rack/session/memcache.rb +59 -47
  40. data/lib/rack/session/pool.rb +56 -29
  41. data/lib/rack/showexceptions.rb +2 -1
  42. data/lib/rack/showstatus.rb +1 -1
  43. data/lib/rack/urlmap.rb +12 -5
  44. data/lib/rack/utils.rb +115 -65
  45. data/rack.gemspec +54 -0
  46. data/test/multipart/binary +0 -0
  47. data/test/multipart/empty +10 -0
  48. data/test/multipart/ie +6 -0
  49. data/test/multipart/nested +10 -0
  50. data/test/multipart/none +9 -0
  51. data/test/multipart/text +10 -0
  52. data/test/spec_rack_auth_basic.rb +5 -1
  53. data/test/spec_rack_auth_digest.rb +93 -36
  54. data/test/spec_rack_auth_openid.rb +47 -100
  55. data/test/spec_rack_builder.rb +2 -2
  56. data/test/spec_rack_chunked.rb +62 -0
  57. data/test/spec_rack_conditionalget.rb +7 -7
  58. data/test/spec_rack_content_type.rb +30 -0
  59. data/test/spec_rack_deflater.rb +36 -14
  60. data/test/spec_rack_directory.rb +1 -1
  61. data/test/spec_rack_file.rb +11 -0
  62. data/test/spec_rack_handler.rb +21 -2
  63. data/test/spec_rack_lint.rb +163 -44
  64. data/test/spec_rack_lock.rb +38 -0
  65. data/test/spec_rack_mock.rb +6 -1
  66. data/test/spec_rack_request.rb +81 -12
  67. data/test/spec_rack_response.rb +46 -2
  68. data/test/spec_rack_rewindable_input.rb +118 -0
  69. data/test/spec_rack_session_memcache.rb +170 -62
  70. data/test/spec_rack_session_pool.rb +129 -41
  71. data/test/spec_rack_static.rb +2 -2
  72. data/test/spec_rack_thin.rb +3 -2
  73. data/test/spec_rack_urlmap.rb +10 -0
  74. data/test/spec_rack_utils.rb +214 -49
  75. data/test/spec_rack_webrick.rb +7 -0
  76. data/test/unregistered_handler/rack/handler/unregistered.rb +7 -0
  77. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +7 -0
  78. metadata +95 -6
  79. data/AUTHORS +0 -8
@@ -14,7 +14,7 @@ context 'Rack::Auth::Digest::MD5' do
14
14
  [ 200, {'Content-Type' => 'text/plain'}, ["Hi #{env['REMOTE_USER']}"] ]
15
15
  end
16
16
  end
17
-
17
+
18
18
  def protected_app
19
19
  app = Rack::Auth::Digest::MD5.new(unprotected_app) do |username|
20
20
  { 'Alice' => 'correct-password' }[username]
@@ -23,7 +23,7 @@ context 'Rack::Auth::Digest::MD5' do
23
23
  app.opaque = 'this-should-be-secret'
24
24
  app
25
25
  end
26
-
26
+
27
27
  def protected_app_with_hashed_passwords
28
28
  app = Rack::Auth::Digest::MD5.new(unprotected_app) do |username|
29
29
  username == 'Alice' ? Digest::MD5.hexdigest("Alice:#{realm}:correct-password") : nil
@@ -33,17 +33,28 @@ context 'Rack::Auth::Digest::MD5' do
33
33
  app.passwords_hashed = true
34
34
  app
35
35
  end
36
-
36
+
37
+ def partially_protected_app
38
+ Rack::URLMap.new({
39
+ '/' => unprotected_app,
40
+ '/protected' => protected_app
41
+ })
42
+ end
43
+
44
+ def protected_app_with_method_override
45
+ Rack::MethodOverride.new(protected_app)
46
+ end
47
+
37
48
  setup do
38
49
  @request = Rack::MockRequest.new(protected_app)
39
50
  end
40
51
 
41
- def request(path, headers = {}, &block)
42
- response = @request.get(path, headers)
52
+ def request(method, path, headers = {}, &block)
53
+ response = @request.request(method, path, headers)
43
54
  block.call(response) if block
44
55
  return response
45
56
  end
46
-
57
+
47
58
  class MockDigestRequest
48
59
  def initialize(params)
49
60
  @params = params
@@ -55,22 +66,25 @@ context 'Rack::Auth::Digest::MD5' do
55
66
  super
56
67
  end
57
68
  def method
58
- 'GET'
69
+ @params['method']
59
70
  end
60
71
  def response(password)
61
72
  Rack::Auth::Digest::MD5.new(nil).send :digest, self, password
62
73
  end
63
74
  end
64
-
65
- def request_with_digest_auth(path, username, password, options = {}, &block)
66
- response = request('/')
67
-
75
+
76
+ def request_with_digest_auth(method, path, username, password, options = {}, &block)
77
+ request_options = {}
78
+ request_options[:input] = options.delete(:input) if options.include? :input
79
+
80
+ response = request(method, path, request_options)
81
+
68
82
  return response unless response.status == 401
69
-
83
+
70
84
  if wait = options.delete(:wait)
71
85
  sleep wait
72
86
  end
73
-
87
+
74
88
  challenge = response['WWW-Authenticate'].split(' ', 2).last
75
89
 
76
90
  params = Rack::Auth::Digest::Params.parse(challenge)
@@ -79,12 +93,14 @@ context 'Rack::Auth::Digest::MD5' do
79
93
  params['nc'] = '00000001'
80
94
  params['cnonce'] = 'nonsensenonce'
81
95
  params['uri'] = path
82
-
96
+
97
+ params['method'] = method
98
+
83
99
  params.update options
84
-
100
+
85
101
  params['response'] = MockDigestRequest.new(params).response(password)
86
-
87
- request(path, { 'HTTP_AUTHORIZATION' => "Digest #{params}" }, &block)
102
+
103
+ request(method, path, request_options.merge('HTTP_AUTHORIZATION' => "Digest #{params}"), &block)
88
104
  end
89
105
 
90
106
  def assert_digest_auth_challenge(response)
@@ -94,7 +110,7 @@ context 'Rack::Auth::Digest::MD5' do
94
110
  response.headers['WWW-Authenticate'].should =~ /^Digest /
95
111
  response.body.should.be.empty
96
112
  end
97
-
113
+
98
114
  def assert_bad_request(response)
99
115
  response.should.be.a.client_error
100
116
  response.status.should.equal 400
@@ -102,44 +118,44 @@ context 'Rack::Auth::Digest::MD5' do
102
118
  end
103
119
 
104
120
  specify 'should challenge when no credentials are specified' do
105
- request '/' do |response|
121
+ request 'GET', '/' do |response|
106
122
  assert_digest_auth_challenge response
107
123
  end
108
124
  end
109
-
125
+
110
126
  specify 'should return application output if correct credentials given' do
111
- request_with_digest_auth '/', 'Alice', 'correct-password' do |response|
127
+ request_with_digest_auth 'GET', '/', 'Alice', 'correct-password' do |response|
112
128
  response.status.should.equal 200
113
129
  response.body.to_s.should.equal 'Hi Alice'
114
130
  end
115
- end
131
+ end
116
132
 
117
133
  specify 'should return application output if correct credentials given (hashed passwords)' do
118
134
  @request = Rack::MockRequest.new(protected_app_with_hashed_passwords)
119
-
120
- request_with_digest_auth '/', 'Alice', 'correct-password' do |response|
135
+
136
+ request_with_digest_auth 'GET', '/', 'Alice', 'correct-password' do |response|
121
137
  response.status.should.equal 200
122
138
  response.body.to_s.should.equal 'Hi Alice'
123
139
  end
124
- end
125
-
140
+ end
141
+
126
142
  specify 'should rechallenge if incorrect username given' do
127
- request_with_digest_auth '/', 'Bob', 'correct-password' do |response|
143
+ request_with_digest_auth 'GET', '/', 'Bob', 'correct-password' do |response|
128
144
  assert_digest_auth_challenge response
129
145
  end
130
146
  end
131
-
147
+
132
148
  specify 'should rechallenge if incorrect password given' do
133
- request_with_digest_auth '/', 'Alice', 'wrong-password' do |response|
149
+ request_with_digest_auth 'GET', '/', 'Alice', 'wrong-password' do |response|
134
150
  assert_digest_auth_challenge response
135
151
  end
136
152
  end
137
-
153
+
138
154
  specify 'should rechallenge with stale parameter if nonce is stale' do
139
155
  begin
140
156
  Rack::Auth::Digest::Nonce.time_limit = 1
141
-
142
- request_with_digest_auth '/', 'Alice', 'correct-password', :wait => 2 do |response|
157
+
158
+ request_with_digest_auth 'GET', '/', 'Alice', 'correct-password', :wait => 2 do |response|
143
159
  assert_digest_auth_challenge response
144
160
  response.headers['WWW-Authenticate'].should =~ /\bstale=true\b/
145
161
  end
@@ -149,21 +165,62 @@ context 'Rack::Auth::Digest::MD5' do
149
165
  end
150
166
 
151
167
  specify 'should return 400 Bad Request if incorrect qop given' do
152
- request_with_digest_auth '/', 'Alice', 'correct-password', 'qop' => 'auth-int' do |response|
168
+ request_with_digest_auth 'GET', '/', 'Alice', 'correct-password', 'qop' => 'auth-int' do |response|
153
169
  assert_bad_request response
154
170
  end
155
171
  end
156
172
 
157
173
  specify 'should return 400 Bad Request if incorrect uri given' do
158
- request_with_digest_auth '/', 'Alice', 'correct-password', 'uri' => '/foo' do |response|
174
+ request_with_digest_auth 'GET', '/', 'Alice', 'correct-password', 'uri' => '/foo' do |response|
159
175
  assert_bad_request response
160
176
  end
161
177
  end
162
178
 
163
179
  specify 'should return 400 Bad Request if different auth scheme used' do
164
- request '/', 'HTTP_AUTHORIZATION' => 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==' do |response|
180
+ request 'GET', '/', 'HTTP_AUTHORIZATION' => 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==' do |response|
165
181
  assert_bad_request response
166
182
  end
167
183
  end
168
-
184
+
185
+ specify 'should not require credentials for unprotected path' do
186
+ @request = Rack::MockRequest.new(partially_protected_app)
187
+ request 'GET', '/' do |response|
188
+ response.should.be.ok
189
+ end
190
+ end
191
+
192
+ specify 'should challenge when no credentials are specified for protected path' do
193
+ @request = Rack::MockRequest.new(partially_protected_app)
194
+ request 'GET', '/protected' do |response|
195
+ assert_digest_auth_challenge response
196
+ end
197
+ end
198
+
199
+ specify 'should return application output if correct credentials given for protected path' do
200
+ @request = Rack::MockRequest.new(partially_protected_app)
201
+ request_with_digest_auth 'GET', '/protected', 'Alice', 'correct-password' do |response|
202
+ response.status.should.equal 200
203
+ response.body.to_s.should.equal 'Hi Alice'
204
+ end
205
+ end
206
+
207
+ specify 'should return application output if correct credentials given for POST' do
208
+ request_with_digest_auth 'POST', '/', 'Alice', 'correct-password' do |response|
209
+ response.status.should.equal 200
210
+ response.body.to_s.should.equal 'Hi Alice'
211
+ end
212
+ end
213
+
214
+ specify 'should return application output if correct credentials given for PUT (using method override of POST)' do
215
+ @request = Rack::MockRequest.new(protected_app_with_method_override)
216
+ request_with_digest_auth 'POST', '/', 'Alice', 'correct-password', :input => "_method=put" do |response|
217
+ response.status.should.equal 200
218
+ response.body.to_s.should.equal 'Hi Alice'
219
+ end
220
+ end
221
+
222
+ specify 'realm as optional constructor arg' do
223
+ app = Rack::Auth::Digest::MD5.new(unprotected_app, realm) { true }
224
+ assert_equal realm, app.realm
225
+ end
169
226
  end
@@ -6,129 +6,76 @@ require 'rack/auth/openid'
6
6
 
7
7
  context "Rack::Auth::OpenID" do
8
8
  OID = Rack::Auth::OpenID
9
- realm = 'http://path/arf'
10
- ruri = %w{arf arf/blargh}
11
- auri = ruri.map{|u|'/'+u}
12
- furi = auri.map{|u|'http://path'+u}
9
+ host = 'host'
10
+ subd = 'sub.host'
11
+ wild = '*.host'
12
+ path = 'path'
13
+ long = 'path/long'
14
+ scheme = 'http://'
15
+ realm = scheme+host+'/'+path
13
16
 
14
- specify 'realm uri should be absolute and have a path' do
15
- lambda{OID.new('/path')}.
16
- should.raise ArgumentError
17
- lambda{OID.new('http://path')}.
18
- should.raise ArgumentError
19
- lambda{OID.new('http://path/')}.
20
- should.not.raise
21
- lambda{OID.new('http://path/arf')}.
22
- should.not.raise
17
+ specify 'realm uri should be valid' do
18
+ lambda{OID.new('/'+path)}.should.raise ArgumentError
19
+ lambda{OID.new('/'+long)}.should.raise ArgumentError
20
+ lambda{OID.new(scheme+host)}.should.not.raise
21
+ lambda{OID.new(scheme+host+'/')}.should.not.raise
22
+ lambda{OID.new(scheme+host+'/'+path)}.should.not.raise
23
+ lambda{OID.new(scheme+subd)}.should.not.raise
24
+ lambda{OID.new(scheme+subd+'/')}.should.not.raise
25
+ lambda{OID.new(scheme+subd+'/'+path)}.should.not.raise
23
26
  end
24
27
 
25
- specify 'uri options should be absolute' do
26
- [:login_good, :login_fail, :login_quit, :return_to].each do |param|
27
- ruri.each do |uri|
28
- lambda{OID.new(realm, {param=>uri})}.
29
- should.raise ArgumentError
30
- end
31
- auri.each do |uri|
32
- lambda{OID.new(realm, {param=>uri})}.
33
- should.raise ArgumentError
34
- end
35
- furi.each do |uri|
36
- lambda{OID.new(realm, {param=>uri})}.
37
- should.not.raise
38
- end
39
- end
28
+ specify 'should be able to check if a uri is within the realm' do
40
29
  end
41
30
 
42
- specify 'return_to should be absolute and be under the realm' do
43
- lambda{OID.new(realm, {:return_to => 'http://path'})}.
44
- should.raise ArgumentError
45
- lambda{OID.new(realm, {:return_to => 'http://path/'})}.
46
- should.raise ArgumentError
47
- lambda{OID.new(realm, {:return_to => 'http://path/arf'})}.
48
- should.not.raise
49
- lambda{OID.new(realm, {:return_to => 'http://path/arf/'})}.
50
- should.not.raise
51
- lambda{OID.new(realm, {:return_to => 'http://path/arf/blargh'})}.
52
- should.not.raise
53
- end
54
-
55
- specify 'extensions should be a module' do
56
- ext = Object.new
57
- lambda{OID.new(realm).add_extension(ext)}.
58
- should.raise(TypeError).
59
- message.should.match(/not a module/)
60
- ext2 = Module.new
61
- lambda{OID.new(realm).add_extension(ext2)}.
62
- should.raise(ArgumentError).
63
- message.should.not.match(/not a module/)
31
+ specify 'return_to should be valid' do
32
+ uri = '/'+path
33
+ lambda{OID.new(realm, :return_to=>uri)}.should.raise ArgumentError
34
+ uri = '/'+long
35
+ lambda{OID.new(realm, :return_to=>uri)}.should.raise ArgumentError
36
+ uri = scheme+host
37
+ lambda{OID.new(realm, :return_to=>uri)}.should.raise ArgumentError
38
+ uri = scheme+host+'/'+path
39
+ lambda{OID.new(realm, :return_to=>uri)}.should.not.raise
40
+ uri = scheme+subd+'/'+path
41
+ lambda{OID.new(realm, :return_to=>uri)}.should.raise ArgumentError
42
+ uri = scheme+host+'/'+long
43
+ lambda{OID.new(realm, :return_to=>uri)}.should.not.raise
44
+ uri = scheme+subd+'/'+long
45
+ lambda{OID.new(realm, :return_to=>uri)}.should.raise ArgumentError
64
46
  end
65
47
 
66
48
  specify 'extensions should have required constants defined' do
49
+ badext = Rack::Auth::OpenID::BadExtension
50
+ ext = Object.new
51
+ lambda{OID.new(realm).add_extension(ext)}.should.raise(badext)
67
52
  ext = Module.new
68
- lambda{OID.new(realm).add_extension(ext)}.
69
- should.raise(ArgumentError).
70
- message.should.match(/missing/)
53
+ lambda{OID.new(realm).add_extension(ext)}.should.raise(badext)
71
54
  ext::Request = nil
72
- lambda{OID.new(realm).add_extension(ext)}.
73
- should.raise(ArgumentError).
74
- message.should.match(/missing/).
75
- should.not.match(/Request/)
55
+ lambda{OID.new(realm).add_extension(ext)}.should.raise(badext)
76
56
  ext::Response = nil
77
- lambda{OID.new(realm).add_extension(ext)}.
78
- should.raise(ArgumentError).
79
- message.should.match(/missing/).
80
- should.not.match(/Response/)
57
+ lambda{OID.new(realm).add_extension(ext)}.should.raise(badext)
81
58
  ext::NS_URI = nil
82
- lambda{OID.new(realm).add_extension(ext)}.
83
- should.raise(TypeError).
84
- message.should.not.match(/missing/)
59
+ lambda{OID.new(realm).add_extension(ext)}.should.raise(badext)
85
60
  end
86
61
 
87
62
  specify 'extensions should have Request and Response defined and inherit from OpenID::Extension' do
88
- $-w, w = nil, $-w # yuck
63
+ $-w, w = nil, $-w # yuck
64
+ badext = Rack::Auth::OpenID::BadExtension
89
65
  ext = Module.new
90
66
  ext::Request = nil
91
67
  ext::Response = nil
92
68
  ext::NS_URI = nil
93
- lambda{OID.new(realm).add_extension(ext)}.
94
- should.raise(TypeError).
95
- message.should.match(/not a class/)
69
+ lambda{OID.new(realm).add_extension(ext)}.should.raise(badext)
96
70
  ext::Request = Class.new()
97
- lambda{OID.new(realm).add_extension(ext)}.
98
- should.raise(TypeError).
99
- message.should.match(/not a class/)
71
+ lambda{OID.new(realm).add_extension(ext)}.should.raise(badext)
100
72
  ext::Response = Class.new()
101
- lambda{OID.new(realm).add_extension(ext)}.
102
- should.raise(ArgumentError).
103
- message.should.match(/not a decendant/)
104
- ext::Request = Class.new(::OpenID::Extension)
105
- lambda{OID.new(realm).add_extension(ext)}.
106
- should.raise(ArgumentError).
107
- message.should.match(/not a decendant/)
108
- ext::Response = Class.new(::OpenID::Extension)
109
- lambda{OID.new(realm).add_extension(ext)}.
110
- should.raise(TypeError).
111
- message.should.match(/NS_URI/)
112
- $-w = w
113
- end
114
-
115
- specify 'extensions should have NS_URI defined and be a string of an absolute http uri' do
116
- $-w, w = nil, $-w # yuck
117
- ext = Module.new
73
+ lambda{OID.new(realm).add_extension(ext)}.should.raise(badext)
118
74
  ext::Request = Class.new(::OpenID::Extension)
75
+ lambda{OID.new(realm).add_extension(ext)}.should.raise(badext)
119
76
  ext::Response = Class.new(::OpenID::Extension)
120
- ext::NS_URI = nil
121
- lambda{OID.new(realm).add_extension(ext)}.
122
- should.raise(TypeError).
123
- message.should.match(/not a string/)
124
- ext::NS_URI = 'openid.net'
125
- lambda{OID.new(realm).add_extension(ext)}.
126
- should.raise(ArgumentError).
127
- message.should.match(/not an http uri/)
128
- ext::NS_URI = 'http://openid.net'
129
- lambda{OID.new(realm).add_extension(ext)}.
130
- should.not.raise
131
- $-w = w
77
+ lambda{OID.new(realm).add_extension(ext)}.should.raise(badext)
78
+ $-w = w
132
79
  end
133
80
  end
134
81
 
@@ -35,7 +35,7 @@ context "Rack::Builder" do
35
35
  'secret' == password
36
36
  end
37
37
 
38
- run lambda { |env| [200, {}, 'Hi Boss'] }
38
+ run lambda { |env| [200, {}, ['Hi Boss']] }
39
39
  end
40
40
 
41
41
  response = Rack::MockRequest.new(app).get("/")
@@ -69,7 +69,7 @@ context "Rack::Builder" do
69
69
  def call(env)
70
70
  raise "bzzzt" if @called > 0
71
71
  @called += 1
72
- [200, {'Content-Type' => 'text/plain'}, 'OK']
72
+ [200, {'Content-Type' => 'text/plain'}, ['OK']]
73
73
  end
74
74
  end
75
75
 
@@ -0,0 +1,62 @@
1
+ require 'rack/mock'
2
+ require 'rack/chunked'
3
+ require 'rack/utils'
4
+
5
+ context "Rack::Chunked" do
6
+
7
+ before do
8
+ @env = Rack::MockRequest.
9
+ env_for('/', 'HTTP_VERSION' => '1.1', 'REQUEST_METHOD' => 'GET')
10
+ end
11
+
12
+ specify 'chunks responses with no Content-Length' do
13
+ app = lambda { |env| [200, {}, ['Hello', ' ', 'World!']] }
14
+ response = Rack::MockResponse.new(*Rack::Chunked.new(app).call(@env))
15
+ response.headers.should.not.include 'Content-Length'
16
+ response.headers['Transfer-Encoding'].should.equal 'chunked'
17
+ response.body.should.equal "5\r\nHello\r\n1\r\n \r\n6\r\nWorld!\r\n0\r\n\r\n"
18
+ end
19
+
20
+ specify 'chunks empty bodies properly' do
21
+ app = lambda { |env| [200, {}, []] }
22
+ response = Rack::MockResponse.new(*Rack::Chunked.new(app).call(@env))
23
+ response.headers.should.not.include 'Content-Length'
24
+ response.headers['Transfer-Encoding'].should.equal 'chunked'
25
+ response.body.should.equal "0\r\n\r\n"
26
+ end
27
+
28
+ specify 'does not modify response when Content-Length header present' do
29
+ app = lambda { |env| [200, {'Content-Length'=>'12'}, ['Hello', ' ', 'World!']] }
30
+ status, headers, body = Rack::Chunked.new(app).call(@env)
31
+ status.should.equal 200
32
+ headers.should.not.include 'Transfer-Encoding'
33
+ headers.should.include 'Content-Length'
34
+ body.join.should.equal 'Hello World!'
35
+ end
36
+
37
+ specify 'does not modify response when client is HTTP/1.0' do
38
+ app = lambda { |env| [200, {}, ['Hello', ' ', 'World!']] }
39
+ @env['HTTP_VERSION'] = 'HTTP/1.0'
40
+ status, headers, body = Rack::Chunked.new(app).call(@env)
41
+ status.should.equal 200
42
+ headers.should.not.include 'Transfer-Encoding'
43
+ body.join.should.equal 'Hello World!'
44
+ end
45
+
46
+ specify 'does not modify response when Transfer-Encoding header already present' do
47
+ app = lambda { |env| [200, {'Transfer-Encoding' => 'identity'}, ['Hello', ' ', 'World!']] }
48
+ status, headers, body = Rack::Chunked.new(app).call(@env)
49
+ status.should.equal 200
50
+ headers['Transfer-Encoding'].should.equal 'identity'
51
+ body.join.should.equal 'Hello World!'
52
+ end
53
+
54
+ [100, 204, 304].each do |status_code|
55
+ specify "does not modify response when status code is #{status_code}" do
56
+ app = lambda { |env| [status_code, {}, []] }
57
+ status, headers, body = Rack::Chunked.new(app).call(@env)
58
+ status.should.equal status_code
59
+ headers.should.not.include 'Transfer-Encoding'
60
+ end
61
+ end
62
+ end