roda 1.0.0 → 1.1.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG +34 -0
- data/README.rdoc +18 -13
- data/Rakefile +8 -0
- data/doc/conventions.rdoc +163 -0
- data/doc/release_notes/1.1.0.txt +226 -0
- data/lib/roda.rb +51 -22
- data/lib/roda/plugins/assets.rb +613 -0
- data/lib/roda/plugins/caching.rb +215 -0
- data/lib/roda/plugins/chunked.rb +278 -0
- data/lib/roda/plugins/error_email.rb +112 -0
- data/lib/roda/plugins/flash.rb +3 -3
- data/lib/roda/plugins/hooks.rb +1 -1
- data/lib/roda/plugins/indifferent_params.rb +3 -3
- data/lib/roda/plugins/middleware.rb +3 -8
- data/lib/roda/plugins/multi_route.rb +110 -18
- data/lib/roda/plugins/not_allowed.rb +3 -3
- data/lib/roda/plugins/path.rb +38 -0
- data/lib/roda/plugins/render.rb +18 -16
- data/lib/roda/plugins/render_each.rb +0 -2
- data/lib/roda/plugins/streaming.rb +1 -2
- data/lib/roda/plugins/view_subdirs.rb +7 -1
- data/lib/roda/version.rb +1 -1
- data/spec/assets/css/app.scss +1 -0
- data/spec/assets/css/no_access.css +1 -0
- data/spec/assets/css/raw.css +1 -0
- data/spec/assets/js/head/app.js +1 -0
- data/spec/integration_spec.rb +95 -3
- data/spec/matchers_spec.rb +2 -2
- data/spec/plugin/assets_spec.rb +413 -0
- data/spec/plugin/caching_spec.rb +335 -0
- data/spec/plugin/chunked_spec.rb +182 -0
- data/spec/plugin/default_headers_spec.rb +6 -5
- data/spec/plugin/error_email_spec.rb +76 -0
- data/spec/plugin/multi_route_spec.rb +120 -0
- data/spec/plugin/not_allowed_spec.rb +14 -3
- data/spec/plugin/path_spec.rb +29 -0
- data/spec/plugin/render_each_spec.rb +6 -1
- data/spec/plugin/symbol_matchers_spec.rb +7 -2
- data/spec/request_spec.rb +10 -0
- data/spec/response_spec.rb +47 -0
- data/spec/views/about.erb +1 -0
- data/spec/views/about.str +1 -0
- data/spec/views/content-yield.erb +1 -0
- data/spec/views/home.erb +2 -0
- data/spec/views/home.str +2 -0
- data/spec/views/layout-alternative.erb +2 -0
- data/spec/views/layout-yield.erb +3 -0
- data/spec/views/layout.erb +2 -0
- data/spec/views/layout.str +2 -0
- metadata +57 -2
@@ -0,0 +1,335 @@
|
|
1
|
+
require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
|
2
|
+
|
3
|
+
describe 'response.cache_control' do
|
4
|
+
it 'sets the Cache-Control header' do
|
5
|
+
app(:caching) do |r|
|
6
|
+
response.cache_control :public=>true, :no_cache=>true, :max_age => 60
|
7
|
+
end
|
8
|
+
header('Cache-Control').split(', ').sort.should == ['max-age=60', 'no-cache', 'public']
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'does not add a Cache-Control header if it would be empty' do
|
12
|
+
app(:caching) do |r|
|
13
|
+
response.cache_control({})
|
14
|
+
end
|
15
|
+
header('Cache-Control').should == nil
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe 'response.expires' do
|
20
|
+
it 'sets the Cache-Control and Expires header' do
|
21
|
+
app(:caching) do |r|
|
22
|
+
response.expires 60, :public=>true, :no_cache=>true
|
23
|
+
end
|
24
|
+
header('Cache-Control').split(', ').sort.should == ['max-age=60', 'no-cache', 'public']
|
25
|
+
((Time.httpdate(header('Expires')) - Time.now).round - 60).abs.should <= 1
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'can be called with only one argument' do
|
29
|
+
app(:caching) do |r|
|
30
|
+
response.expires 60
|
31
|
+
end
|
32
|
+
header('Cache-Control').split(', ').sort.should == ['max-age=60']
|
33
|
+
((Time.httpdate(header('Expires')) - Time.now).round - 60).abs.should <= 1
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe 'response.finish' do
|
38
|
+
it 'removes Content-Type and Content-Length for 304 responses' do
|
39
|
+
app(:caching) do |r|
|
40
|
+
response.status = 304
|
41
|
+
end
|
42
|
+
header('Content-Type').should == nil
|
43
|
+
header('Content-Length').should == nil
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'does not change non-304 responses' do
|
47
|
+
app(:caching) do |r|
|
48
|
+
response.status = 200
|
49
|
+
end
|
50
|
+
header('Content-Type').should == 'text/html'
|
51
|
+
header('Content-Length').should == '0'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe 'request.last_modified' do
|
56
|
+
it 'ignores nil' do
|
57
|
+
app(:caching) do |r|
|
58
|
+
r.last_modified nil
|
59
|
+
end
|
60
|
+
header('Last-Modified').should == nil
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'does not change a status other than 200' do
|
64
|
+
app(:caching) do |r|
|
65
|
+
response.status = 201
|
66
|
+
r.last_modified Time.now
|
67
|
+
end
|
68
|
+
status.should == 201
|
69
|
+
status('HTTP_IF_MODIFIED_SINCE' => 'Sun, 26 Sep 2030 23:43:52 GMT').should == 201
|
70
|
+
status('HTTP_IF_MODIFIED_SINCE' => 'Sun, 26 Sep 2000 23:43:52 GMT').should == 201
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe 'request.last_modified' do
|
75
|
+
def res(a={})
|
76
|
+
s, h, b = req(a)
|
77
|
+
h['Last-Modified'].should == @last_modified.httpdate
|
78
|
+
[s, b.join]
|
79
|
+
end
|
80
|
+
|
81
|
+
before(:all) do
|
82
|
+
lm = @last_modified = Time.now
|
83
|
+
app(:caching) do |r|
|
84
|
+
r.last_modified lm
|
85
|
+
'ok'
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'just sets Last-Modified if no If-Modified-Since header' do
|
90
|
+
res.should == [200, 'ok']
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'just sets Last-Modified if bogus If-Modified-Since header' do
|
94
|
+
res('HTTP_IF_MODIFIED_SINCE' => 'a really weird date').should == [200, 'ok']
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'just sets Last-Modified if modified since If-Modified-Since header' do
|
98
|
+
res('HTTP_IF_MODIFIED_SINCE' => (@last_modified - 1).httpdate).should == [200, 'ok']
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'sets Last-Modified and returns 304 if modified on If-Modified-Since header' do
|
102
|
+
res('HTTP_IF_MODIFIED_SINCE' => @last_modified.httpdate).should == [304, '']
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'sets Last-Modified and returns 304 if modified before If-Modified-Since header' do
|
106
|
+
res('HTTP_IF_MODIFIED_SINCE' => (@last_modified + 1).httpdate).should == [304, '']
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'sets Last-Modified if If-None-Match header present' do
|
110
|
+
res('HTTP_IF_NONE_MATCH' => '*', 'HTTP_IF_MODIFIED_SINCE' => (@last_modified + 1).httpdate).should == [200, 'ok']
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'sets Last-Modified if modified before If-Unmodified-Since header' do
|
114
|
+
res('HTTP_IF_UNMODIFIED_SINCE' => (@last_modified + 1).httpdate).should == [200, 'ok']
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'sets Last-Modified if modified on If-Unmodified-Since header' do
|
118
|
+
res('HTTP_IF_UNMODIFIED_SINCE' => @last_modified.httpdate).should == [200, 'ok']
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'sets Last-Modified and returns 412 if modified after If-Unmodified-Since header' do
|
122
|
+
res('HTTP_IF_UNMODIFIED_SINCE' => (@last_modified - 1).httpdate).should == [412, '']
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe 'request.etag' do
|
127
|
+
before(:all) do
|
128
|
+
app(:caching) do |r|
|
129
|
+
r.is "" do
|
130
|
+
response.status = r.env['status'] if r.env['status']
|
131
|
+
etag_opts = {}
|
132
|
+
etag_opts[:new_resource] = r.env['new_resource'] if r.env.has_key?('new_resource')
|
133
|
+
etag_opts[:weak] = r.env['weak'] if r.env.has_key?('weak')
|
134
|
+
r.etag 'foo', etag_opts
|
135
|
+
'ok'
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'uses a weak etag with the :weak option' do
|
141
|
+
header('ETag', 'weak'=>true).should == 'W/"foo"'
|
142
|
+
end
|
143
|
+
|
144
|
+
describe 'for GET requests' do
|
145
|
+
def res(a={})
|
146
|
+
s, h, b = req(a)
|
147
|
+
h['ETag'].should == '"foo"'
|
148
|
+
[s, b.join]
|
149
|
+
end
|
150
|
+
|
151
|
+
it "sets etag if no If-None-Match" do
|
152
|
+
res.should == [200, 'ok']
|
153
|
+
end
|
154
|
+
|
155
|
+
it "sets etag and returns 304 if If-None-Match is *" do
|
156
|
+
res('HTTP_IF_NONE_MATCH' => '*').should == [304, '']
|
157
|
+
end
|
158
|
+
|
159
|
+
it "sets etag and if If-None-Match is * and it is a new resource" do
|
160
|
+
res('HTTP_IF_NONE_MATCH' => '*', 'new_resource'=>true).should == [200, 'ok']
|
161
|
+
end
|
162
|
+
|
163
|
+
it "sets etag and returns 304 if If-None-Match is etag" do
|
164
|
+
res('HTTP_IF_NONE_MATCH' => '"foo"').should == [304, '']
|
165
|
+
end
|
166
|
+
|
167
|
+
it "sets etag and returns 304 if If-None-Match includes etag" do
|
168
|
+
res('HTTP_IF_NONE_MATCH' => '"bar", "foo"').should == [304, '']
|
169
|
+
end
|
170
|
+
|
171
|
+
it "sets etag if If-None-Match does not include etag" do
|
172
|
+
res('HTTP_IF_NONE_MATCH' => '"bar", "baz"').should == [200, 'ok']
|
173
|
+
end
|
174
|
+
|
175
|
+
it "sets etag and does not change status code if status code set and not 2xx or 304 if If-None-Match is etag" do
|
176
|
+
res('HTTP_IF_NONE_MATCH' => '"foo"', 'status'=>499).should == [499, 'ok']
|
177
|
+
end
|
178
|
+
|
179
|
+
it "sets etag and returns 304 if status code set to 2xx if If-None-Match is etag" do
|
180
|
+
res('HTTP_IF_NONE_MATCH' => '"foo"', 'status'=>201).should == [304, '']
|
181
|
+
end
|
182
|
+
|
183
|
+
it "sets etag and returns 304 if status code is already 304 if If-None-Match is etag" do
|
184
|
+
res('HTTP_IF_NONE_MATCH' => '"foo"', 'status'=>304).should == [304, '']
|
185
|
+
end
|
186
|
+
|
187
|
+
it "sets etag if If-Match is *" do
|
188
|
+
res('HTTP_IF_MATCH' => '*').should == [200, 'ok']
|
189
|
+
end
|
190
|
+
|
191
|
+
it "sets etag if If-Match is etag" do
|
192
|
+
res('HTTP_IF_MATCH' => '"foo"').should == [200, 'ok']
|
193
|
+
end
|
194
|
+
|
195
|
+
it "sets etag if If-Match includes etag" do
|
196
|
+
res('HTTP_IF_MATCH' => '"bar", "foo"').should == [200, 'ok']
|
197
|
+
end
|
198
|
+
|
199
|
+
it "sets etag and returns 412 if If-Match is * for new resources" do
|
200
|
+
res('HTTP_IF_MATCH' => '*', 'new_resource'=>true).should == [412, '']
|
201
|
+
end
|
202
|
+
|
203
|
+
it "sets etag if If-Match does not include etag" do
|
204
|
+
res('HTTP_IF_MATCH' => '"bar", "baz"', 'new_resource'=>true).should == [412, '']
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
describe 'for PUT requests' do
|
209
|
+
def res(a={})
|
210
|
+
s, h, b = req(a.merge('REQUEST_METHOD'=>'PUT'))
|
211
|
+
h['ETag'].should == '"foo"'
|
212
|
+
[s, b.join]
|
213
|
+
end
|
214
|
+
|
215
|
+
it "sets etag if no If-None-Match" do
|
216
|
+
res.should == [200, 'ok']
|
217
|
+
end
|
218
|
+
|
219
|
+
it "sets etag and returns 412 if If-None-Match is *" do
|
220
|
+
res('HTTP_IF_NONE_MATCH' => '*').should == [412, '']
|
221
|
+
end
|
222
|
+
|
223
|
+
it "sets etag and if If-None-Match is * and it is a new resource" do
|
224
|
+
res('HTTP_IF_NONE_MATCH' => '*', 'new_resource'=>true).should == [200, 'ok']
|
225
|
+
end
|
226
|
+
|
227
|
+
it "sets etag and returns 412 if If-None-Match is etag" do
|
228
|
+
res('HTTP_IF_NONE_MATCH' => '"foo"').should == [412, '']
|
229
|
+
end
|
230
|
+
|
231
|
+
it "sets etag and returns 412 if If-None-Match includes etag" do
|
232
|
+
res('HTTP_IF_NONE_MATCH' => '"bar", "foo"').should == [412, '']
|
233
|
+
end
|
234
|
+
|
235
|
+
it "sets etag if If-None-Match does not include etag" do
|
236
|
+
res('HTTP_IF_NONE_MATCH' => '"bar", "baz"').should == [200, 'ok']
|
237
|
+
end
|
238
|
+
|
239
|
+
it "sets etag and does not change status code if status code set and not 2xx or 304 if If-None-Match is etag" do
|
240
|
+
res('HTTP_IF_NONE_MATCH' => '"foo"', 'status'=>499).should == [499, 'ok']
|
241
|
+
end
|
242
|
+
|
243
|
+
it "sets etag and returns 304 if status code set to 2xx if If-None-Match is etag" do
|
244
|
+
res('HTTP_IF_NONE_MATCH' => '"foo"', 'status'=>201).should == [412, '']
|
245
|
+
end
|
246
|
+
|
247
|
+
it "sets etag and returns 304 if status code is already 304 if If-None-Match is etag" do
|
248
|
+
res('HTTP_IF_NONE_MATCH' => '"foo"', 'status'=>304).should == [412, '']
|
249
|
+
end
|
250
|
+
|
251
|
+
it "sets etag if If-Match is *" do
|
252
|
+
res('HTTP_IF_MATCH' => '*').should == [200, 'ok']
|
253
|
+
end
|
254
|
+
|
255
|
+
it "sets etag if If-Match is etag" do
|
256
|
+
res('HTTP_IF_MATCH' => '"foo"').should == [200, 'ok']
|
257
|
+
end
|
258
|
+
|
259
|
+
it "sets etag if If-Match includes etag" do
|
260
|
+
res('HTTP_IF_MATCH' => '"bar", "foo"').should == [200, 'ok']
|
261
|
+
end
|
262
|
+
|
263
|
+
it "sets etag and returns 412 if If-Match is * for new resources" do
|
264
|
+
res('HTTP_IF_MATCH' => '*', 'new_resource'=>true).should == [412, '']
|
265
|
+
end
|
266
|
+
|
267
|
+
it "sets etag if If-Match does not include etag" do
|
268
|
+
res('HTTP_IF_MATCH' => '"bar", "baz"', 'new_resource'=>true).should == [412, '']
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
describe 'for POST requests' do
|
273
|
+
def res(a={})
|
274
|
+
s, h, b = req(a.merge('REQUEST_METHOD'=>'POST'))
|
275
|
+
h['ETag'].should == '"foo"'
|
276
|
+
[s, b.join]
|
277
|
+
end
|
278
|
+
|
279
|
+
it "sets etag if no If-None-Match" do
|
280
|
+
res.should == [200, 'ok']
|
281
|
+
end
|
282
|
+
|
283
|
+
it "sets etag and returns 412 if If-None-Match is * and it is not a new resource" do
|
284
|
+
res('HTTP_IF_NONE_MATCH' => '*', 'new_resource'=>false).should == [412, '']
|
285
|
+
end
|
286
|
+
|
287
|
+
it "sets etag and if If-None-Match is *" do
|
288
|
+
res('HTTP_IF_NONE_MATCH' => '*').should == [200, 'ok']
|
289
|
+
end
|
290
|
+
|
291
|
+
it "sets etag and returns 412 if If-None-Match is etag" do
|
292
|
+
res('HTTP_IF_NONE_MATCH' => '"foo"').should == [412, '']
|
293
|
+
end
|
294
|
+
|
295
|
+
it "sets etag and returns 412 if If-None-Match includes etag" do
|
296
|
+
res('HTTP_IF_NONE_MATCH' => '"bar", "foo"').should == [412, '']
|
297
|
+
end
|
298
|
+
|
299
|
+
it "sets etag if If-None-Match does not include etag" do
|
300
|
+
res('HTTP_IF_NONE_MATCH' => '"bar", "baz"').should == [200, 'ok']
|
301
|
+
end
|
302
|
+
|
303
|
+
it "sets etag and does not change status code if status code set and not 2xx or 304 if If-None-Match is etag" do
|
304
|
+
res('HTTP_IF_NONE_MATCH' => '"foo"', 'status'=>499).should == [499, 'ok']
|
305
|
+
end
|
306
|
+
|
307
|
+
it "sets etag and returns 304 if status code set to 2xx if If-None-Match is etag" do
|
308
|
+
res('HTTP_IF_NONE_MATCH' => '"foo"', 'status'=>201).should == [412, '']
|
309
|
+
end
|
310
|
+
|
311
|
+
it "sets etag and returns 304 if status code is already 304 if If-None-Match is etag" do
|
312
|
+
res('HTTP_IF_NONE_MATCH' => '"foo"', 'status'=>304).should == [412, '']
|
313
|
+
end
|
314
|
+
|
315
|
+
it "sets etag if If-Match is * and this is not a new resource" do
|
316
|
+
res('HTTP_IF_MATCH' => '*', 'new_resource'=>false).should == [200, 'ok']
|
317
|
+
end
|
318
|
+
|
319
|
+
it "sets etag if If-Match is etag" do
|
320
|
+
res('HTTP_IF_MATCH' => '"foo"').should == [200, 'ok']
|
321
|
+
end
|
322
|
+
|
323
|
+
it "sets etag if If-Match includes etag" do
|
324
|
+
res('HTTP_IF_MATCH' => '"bar", "foo"').should == [200, 'ok']
|
325
|
+
end
|
326
|
+
|
327
|
+
it "sets etag and returns 412 if If-Match is * for new resources" do
|
328
|
+
res('HTTP_IF_MATCH' => '*').should == [412, '']
|
329
|
+
end
|
330
|
+
|
331
|
+
it "sets etag if If-Match does not include etag" do
|
332
|
+
res('HTTP_IF_MATCH' => '"bar", "baz"', 'new_resource'=>true).should == [412, '']
|
333
|
+
end
|
334
|
+
end
|
335
|
+
end
|
@@ -0,0 +1,182 @@
|
|
1
|
+
require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'tilt/erb'
|
5
|
+
rescue LoadError
|
6
|
+
warn "tilt not installed, skipping chunked plugin test"
|
7
|
+
else
|
8
|
+
describe "chunked plugin" do
|
9
|
+
def cbody(env={})
|
10
|
+
b = ''
|
11
|
+
req({'HTTP_VERSION'=>'HTTP/1.1'}.merge(env))[2].each{|s| b << s}
|
12
|
+
b
|
13
|
+
end
|
14
|
+
|
15
|
+
it "streams templates in chunked encoding only if HTTP 1.1 is used" do
|
16
|
+
app(:chunked) do |r|
|
17
|
+
chunked(:inline=>'m', :layout=>{:inline=>'h<%= yield %>t'})
|
18
|
+
end
|
19
|
+
|
20
|
+
cbody.should == "1\r\nh\r\n1\r\nm\r\n1\r\nt\r\n0\r\n\r\n"
|
21
|
+
body.should == "hmt"
|
22
|
+
end
|
23
|
+
|
24
|
+
it "hex encodes chunk sizes" do
|
25
|
+
m = 'm' * 31
|
26
|
+
app(:chunked) do |r|
|
27
|
+
chunked(:inline=>m, :layout=>{:inline=>'h<%= yield %>t'})
|
28
|
+
end
|
29
|
+
|
30
|
+
cbody.should == "1\r\nh\r\n1f\r\n#{m}\r\n1\r\nt\r\n0\r\n\r\n"
|
31
|
+
body.should == "h#{m}t"
|
32
|
+
end
|
33
|
+
|
34
|
+
it "accepts a block that is called after layout yielding but before content when streaming" do
|
35
|
+
app(:chunked) do |r|
|
36
|
+
chunked(:inline=>'m<%= @h %>', :layout=>{:inline=>'<%= @h %><%= yield %>t'}) do
|
37
|
+
@h = 'h'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
cbody.should == "2\r\nmh\r\n1\r\nt\r\n0\r\n\r\n"
|
42
|
+
body.should == "hmht"
|
43
|
+
end
|
44
|
+
|
45
|
+
it "works when a layout is not used" do
|
46
|
+
app(:chunked) do |r|
|
47
|
+
chunked(:inline=>'m', :layout=>nil)
|
48
|
+
end
|
49
|
+
|
50
|
+
cbody.should == "1\r\nm\r\n0\r\n\r\n"
|
51
|
+
body.should == "m"
|
52
|
+
end
|
53
|
+
|
54
|
+
it "streams partial template responses if flush is used in content template" do
|
55
|
+
app(:chunked) do |r|
|
56
|
+
chunked(:inline=>'m<%= flush %>n', :layout=>{:inline=>'h<%= yield %>t'})
|
57
|
+
end
|
58
|
+
|
59
|
+
cbody.should == "1\r\nh\r\n1\r\nm\r\n1\r\nn\r\n1\r\nt\r\n0\r\n\r\n"
|
60
|
+
body.should == "hmnt"
|
61
|
+
end
|
62
|
+
|
63
|
+
it "streams partial template responses if flush is used in layout template" do
|
64
|
+
app(:chunked) do |r|
|
65
|
+
chunked(:inline=>'m', :layout=>{:inline=>'h<%= flush %>i<%= yield %>t'})
|
66
|
+
end
|
67
|
+
|
68
|
+
cbody.should == "1\r\nh\r\n1\r\ni\r\n1\r\nm\r\n1\r\nt\r\n0\r\n\r\n"
|
69
|
+
body.should == "himt"
|
70
|
+
end
|
71
|
+
|
72
|
+
it "does not stream if no_chunk! is used" do
|
73
|
+
app(:chunked) do |r|
|
74
|
+
no_chunk!
|
75
|
+
chunked(:inline=>'m', :layout=>{:inline=>'h<%= yield %>t'})
|
76
|
+
end
|
77
|
+
|
78
|
+
cbody.should == "hmt"
|
79
|
+
body.should == "hmt"
|
80
|
+
end
|
81
|
+
|
82
|
+
it "streams existing response body before call" do
|
83
|
+
app(:chunked) do |r|
|
84
|
+
response.write('a')
|
85
|
+
response.write chunked(:inline=>'m', :layout=>{:inline=>'h<%= yield %>t'})
|
86
|
+
end
|
87
|
+
|
88
|
+
cbody.should == "1\r\na\r\n1\r\nh\r\n1\r\nm\r\n1\r\nt\r\n0\r\n\r\n"
|
89
|
+
body.should == "ahmt"
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should not include Content-Length header even if body is already written to" do
|
93
|
+
app(:chunked) do |r|
|
94
|
+
response.write('a')
|
95
|
+
response.write chunked(:inline=>'m', :layout=>{:inline=>'h<%= yield %>t'})
|
96
|
+
end
|
97
|
+
|
98
|
+
header('Content-Length', 'HTTP_VERSION'=>'HTTP/1.1').should == nil
|
99
|
+
header('Content-Length', 'HTTP_VERSION'=>'HTTP/1.0').should == '4'
|
100
|
+
end
|
101
|
+
|
102
|
+
it "stream template responses for view if :chunk_by_default is used" do
|
103
|
+
app(:bare) do
|
104
|
+
plugin :chunked, :chunk_by_default=>true
|
105
|
+
route do |r|
|
106
|
+
view(:inline=>'m', :layout=>{:inline=>'h<%= yield %>t'})
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
cbody.should == "1\r\nh\r\n1\r\nm\r\n1\r\nt\r\n0\r\n\r\n"
|
111
|
+
body.should == "hmt"
|
112
|
+
end
|
113
|
+
|
114
|
+
it "uses Transfer-Encoding header when chunking" do
|
115
|
+
app(:chunked) do |r|
|
116
|
+
chunked(:inline=>'m', :layout=>{:inline=>'h<%= yield %>t'})
|
117
|
+
end
|
118
|
+
|
119
|
+
header('Transfer-Encoding', 'HTTP_VERSION'=>'HTTP/1.1').should == 'chunked'
|
120
|
+
header('Transfer-Encoding', 'HTTP_VERSION'=>'HTTP/1.0').should == nil
|
121
|
+
end
|
122
|
+
|
123
|
+
it "uses given :headers when chunking" do
|
124
|
+
app(:bare) do
|
125
|
+
plugin :chunked, :headers=>{'Foo'=>'bar'}
|
126
|
+
route do |r|
|
127
|
+
chunked(:inline=>'m', :layout=>{:inline=>'h<%= yield %>t'})
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
header('Foo', 'HTTP_VERSION'=>'HTTP/1.1').should == 'bar'
|
132
|
+
header('Foo', 'HTTP_VERSION'=>'HTTP/1.0').should == nil
|
133
|
+
end
|
134
|
+
|
135
|
+
it "handles multiple arguments to chunked" do
|
136
|
+
app(:bare) do
|
137
|
+
plugin :chunked, :chunk_by_default=>true
|
138
|
+
plugin :render, :views => "./spec/views"
|
139
|
+
route do |r|
|
140
|
+
chunked('about', :locals=>{:title=>'m'}, :layout=>{:inline=>'h<%= yield %>t'})
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
cbody.should == "1\r\nh\r\nb\r\n<h1>m</h1>\n\r\n1\r\nt\r\n0\r\n\r\n"
|
145
|
+
body.should == "h<h1>m</h1>\nt"
|
146
|
+
end
|
147
|
+
|
148
|
+
it "handles multiple hash arguments to chunked" do
|
149
|
+
app(:chunked) do |r|
|
150
|
+
chunked({:inline=>'m'}, :layout=>{:inline=>'h<%= yield %>t'})
|
151
|
+
end
|
152
|
+
|
153
|
+
cbody.should == "1\r\nh\r\n1\r\nm\r\n1\r\nt\r\n0\r\n\r\n"
|
154
|
+
body.should == "hmt"
|
155
|
+
end
|
156
|
+
|
157
|
+
it "handles :layout_opts option" do
|
158
|
+
app(:chunked) do |r|
|
159
|
+
chunked(:inline=>'m', :layout=>{:inline=>'<%= h %><%= yield %>t'}, :layout_opts=>{:locals=>{:h=>'h'}})
|
160
|
+
end
|
161
|
+
|
162
|
+
cbody.should == "1\r\nh\r\n1\r\nm\r\n1\r\nt\r\n0\r\n\r\n"
|
163
|
+
body.should == "hmt"
|
164
|
+
end
|
165
|
+
|
166
|
+
it "uses handle_chunk_error for handling errors when chunking" do
|
167
|
+
app(:chunked) do |r|
|
168
|
+
chunked(:inline=>'m', :layout=>{:inline=>'h<%= yield %><% raise %>'})
|
169
|
+
end
|
170
|
+
proc{cbody}.should raise_error
|
171
|
+
proc{body}.should raise_error
|
172
|
+
|
173
|
+
app.send(:define_method, :handle_chunk_error) do |v|
|
174
|
+
@_out_buf = 'e'
|
175
|
+
flush
|
176
|
+
end
|
177
|
+
|
178
|
+
cbody.should == "1\r\nh\r\n1\r\nm\r\n1\r\ne\r\n0\r\n\r\n"
|
179
|
+
proc{body}.should raise_error
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|