sinatra-base 1.0 → 1.4.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.
- data/.yardopts +4 -0
- data/AUTHORS +15 -0
- data/CHANGES +524 -1
- data/Gemfile +82 -0
- data/LICENSE +1 -1
- data/README.de.rdoc +2093 -0
- data/README.es.rdoc +2091 -0
- data/README.fr.rdoc +2116 -0
- data/README.hu.rdoc +607 -0
- data/README.jp.rdoc +514 -23
- data/README.pt-br.rdoc +647 -0
- data/README.pt-pt.rdoc +646 -0
- data/README.rdoc +1580 -205
- data/README.ru.rdoc +2015 -0
- data/README.zh.rdoc +1816 -0
- data/Rakefile +110 -44
- data/examples/chat.rb +61 -0
- data/examples/simple.rb +3 -0
- data/examples/stream.ru +26 -0
- data/lib/sinatra.rb +0 -3
- data/lib/sinatra/base.rb +923 -393
- data/lib/sinatra/main.rb +9 -7
- data/lib/sinatra/showexceptions.rb +37 -4
- data/lib/sinatra/version.rb +3 -0
- data/sinatra-base.gemspec +15 -91
- data/test/base_test.rb +2 -2
- data/test/builder_test.rb +32 -2
- data/test/coffee_test.rb +92 -0
- data/test/contest.rb +62 -28
- data/test/creole_test.rb +65 -0
- data/test/delegator_test.rb +162 -0
- data/test/encoding_test.rb +20 -0
- data/test/erb_test.rb +25 -2
- data/test/extensions_test.rb +1 -1
- data/test/filter_test.rb +226 -8
- data/test/haml_test.rb +8 -2
- data/test/helper.rb +47 -0
- data/test/helpers_test.rb +1287 -80
- data/test/integration/app.rb +62 -0
- data/test/integration_helper.rb +208 -0
- data/test/integration_test.rb +82 -0
- data/test/less_test.rb +36 -6
- data/test/liquid_test.rb +59 -0
- data/test/mapped_error_test.rb +84 -7
- data/test/markaby_test.rb +80 -0
- data/test/markdown_test.rb +81 -0
- data/test/middleware_test.rb +1 -1
- data/test/nokogiri_test.rb +69 -0
- data/test/rack_test.rb +45 -0
- data/test/radius_test.rb +59 -0
- data/test/rdoc_test.rb +66 -0
- data/test/readme_test.rb +136 -0
- data/test/request_test.rb +13 -1
- data/test/response_test.rb +21 -2
- data/test/result_test.rb +5 -5
- data/test/route_added_hook_test.rb +1 -1
- data/test/routing_test.rb +328 -13
- data/test/sass_test.rb +48 -18
- data/test/scss_test.rb +88 -0
- data/test/server_test.rb +4 -3
- data/test/settings_test.rb +191 -21
- data/test/sinatra_test.rb +5 -1
- data/test/slim_test.rb +88 -0
- data/test/static_test.rb +89 -5
- data/test/streaming_test.rb +140 -0
- data/test/templates_test.rb +143 -4
- data/test/textile_test.rb +65 -0
- data/test/views/a/in_a.str +1 -0
- data/test/views/ascii.erb +2 -0
- data/test/views/b/in_b.str +1 -0
- data/test/views/calc.html.erb +1 -0
- data/test/views/explicitly_nested.str +1 -0
- data/test/views/hello.coffee +1 -0
- data/test/views/hello.creole +1 -0
- data/test/views/hello.liquid +1 -0
- data/test/views/hello.mab +1 -0
- data/test/views/hello.md +1 -0
- data/test/views/hello.nokogiri +1 -0
- data/test/views/hello.radius +1 -0
- data/test/views/hello.rdoc +1 -0
- data/test/views/hello.sass +1 -1
- data/test/views/hello.scss +3 -0
- data/test/views/hello.slim +1 -0
- data/test/views/hello.str +1 -0
- data/test/views/hello.textile +1 -0
- data/test/views/hello.yajl +1 -0
- data/test/views/layout2.liquid +2 -0
- data/test/views/layout2.mab +2 -0
- data/test/views/layout2.nokogiri +3 -0
- data/test/views/layout2.radius +2 -0
- data/test/views/layout2.slim +3 -0
- data/test/views/layout2.str +2 -0
- data/test/views/nested.str +1 -0
- data/test/views/utf8.erb +2 -0
- data/test/yajl_test.rb +80 -0
- metadata +126 -91
- data/lib/sinatra/tilt.rb +0 -746
- data/test/erubis_test.rb +0 -82
- data/test/views/error.erubis +0 -3
- data/test/views/hello.erubis +0 -1
- data/test/views/layout2.erubis +0 -2
data/test/haml_test.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require File.
|
1
|
+
require File.expand_path('../helper', __FILE__)
|
2
2
|
|
3
3
|
begin
|
4
4
|
require 'haml'
|
@@ -89,7 +89,13 @@ class HAMLTest < Test::Unit::TestCase
|
|
89
89
|
assert ok?
|
90
90
|
assert_match(/^<!DOCTYPE html PUBLIC (.*) HTML 4.01/, body)
|
91
91
|
end
|
92
|
+
|
93
|
+
it "is possible to pass locals" do
|
94
|
+
haml_app { haml "= foo", :locals => { :foo => 'bar' }}
|
95
|
+
assert_equal "bar\n", body
|
96
|
+
end
|
92
97
|
end
|
93
|
-
|
98
|
+
|
99
|
+
rescue LoadError
|
94
100
|
warn "#{$!.to_s}: skipping haml tests"
|
95
101
|
end
|
data/test/helper.rb
CHANGED
@@ -1,4 +1,7 @@
|
|
1
1
|
ENV['RACK_ENV'] = 'test'
|
2
|
+
Encoding.default_external = "UTF-8" if defined? Encoding
|
3
|
+
|
4
|
+
RUBY_ENGINE = 'ruby' unless defined? RUBY_ENGINE
|
2
5
|
|
3
6
|
begin
|
4
7
|
require 'rack'
|
@@ -22,6 +25,12 @@ class Sinatra::Base
|
|
22
25
|
include Test::Unit::Assertions
|
23
26
|
end
|
24
27
|
|
28
|
+
class Rack::Builder
|
29
|
+
def include?(middleware)
|
30
|
+
@ins.any? { |m| p m ; middleware === m }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
25
34
|
Sinatra::Base.set :environment, :test
|
26
35
|
|
27
36
|
class Test::Unit::TestCase
|
@@ -29,6 +38,13 @@ class Test::Unit::TestCase
|
|
29
38
|
|
30
39
|
class << self
|
31
40
|
alias_method :it, :test
|
41
|
+
alias_method :section, :context
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.example(desc = nil, &block)
|
45
|
+
@example_count = 0 unless instance_variable_defined? :@example_count
|
46
|
+
@example_count += 1
|
47
|
+
it(desc || "Example #{@example_count}", &block)
|
32
48
|
end
|
33
49
|
|
34
50
|
alias_method :response, :last_response
|
@@ -52,6 +68,35 @@ class Test::Unit::TestCase
|
|
52
68
|
response.body.to_s
|
53
69
|
end
|
54
70
|
|
71
|
+
def assert_body(value)
|
72
|
+
if value.respond_to? :to_str
|
73
|
+
assert_equal value.lstrip.gsub(/\s*\n\s*/, ""), body.lstrip.gsub(/\s*\n\s*/, "")
|
74
|
+
else
|
75
|
+
assert_match value, body
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def assert_status(expected)
|
80
|
+
assert_equal Integer(expected), Integer(status)
|
81
|
+
end
|
82
|
+
|
83
|
+
def assert_like(a,b)
|
84
|
+
pattern = /id=['"][^"']*["']|\s+/
|
85
|
+
assert_equal a.strip.gsub(pattern, ""), b.strip.gsub(pattern, "")
|
86
|
+
end
|
87
|
+
|
88
|
+
def assert_include(str, substr)
|
89
|
+
assert str.include?(substr), "expected #{str.inspect} to include #{substr.inspect}"
|
90
|
+
end
|
91
|
+
|
92
|
+
def options(uri, params = {}, env = {}, &block)
|
93
|
+
request(uri, env.merge(:method => "OPTIONS", :params => params), &block)
|
94
|
+
end
|
95
|
+
|
96
|
+
def patch(uri, params = {}, env = {}, &block)
|
97
|
+
request(uri, env.merge(:method => "PATCH", :params => params), &block)
|
98
|
+
end
|
99
|
+
|
55
100
|
# Delegate other missing methods to response.
|
56
101
|
def method_missing(name, *args, &block)
|
57
102
|
if response && response.respond_to?(name)
|
@@ -59,6 +104,8 @@ class Test::Unit::TestCase
|
|
59
104
|
else
|
60
105
|
super
|
61
106
|
end
|
107
|
+
rescue Rack::Test::Error
|
108
|
+
super
|
62
109
|
end
|
63
110
|
|
64
111
|
# Also check response since we delegate there.
|
data/test/helpers_test.rb
CHANGED
@@ -1,26 +1,122 @@
|
|
1
|
-
require File.
|
1
|
+
require File.expand_path('../helper', __FILE__)
|
2
|
+
require 'date'
|
2
3
|
|
3
4
|
class HelpersTest < Test::Unit::TestCase
|
4
5
|
def test_default
|
5
6
|
assert true
|
6
7
|
end
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
9
|
+
def status_app(code, &block)
|
10
|
+
code += 2 if [204, 205, 304].include? code
|
11
|
+
block ||= proc { }
|
12
|
+
mock_app do
|
13
|
+
get '/' do
|
14
|
+
status code
|
15
|
+
instance_eval(&block).inspect
|
16
|
+
end
|
16
17
|
end
|
18
|
+
get '/'
|
19
|
+
end
|
17
20
|
|
21
|
+
describe 'status' do
|
18
22
|
it 'sets the response status code' do
|
19
|
-
|
23
|
+
status_app 207
|
20
24
|
assert_equal 207, response.status
|
21
25
|
end
|
22
26
|
end
|
23
27
|
|
28
|
+
describe 'not_found?' do
|
29
|
+
it 'is true for status == 404' do
|
30
|
+
status_app(404) { not_found? }
|
31
|
+
assert_body 'true'
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'is false for status > 404' do
|
35
|
+
status_app(405) { not_found? }
|
36
|
+
assert_body 'false'
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'is false for status < 404' do
|
40
|
+
status_app(403) { not_found? }
|
41
|
+
assert_body 'false'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe 'informational?' do
|
46
|
+
it 'is true for 1xx status' do
|
47
|
+
status_app(100 + rand(100)) { informational? }
|
48
|
+
assert_body 'true'
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'is false for status > 199' do
|
52
|
+
status_app(200 + rand(400)) { informational? }
|
53
|
+
assert_body 'false'
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe 'success?' do
|
58
|
+
it 'is true for 2xx status' do
|
59
|
+
status_app(200 + rand(100)) { success? }
|
60
|
+
assert_body 'true'
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'is false for status < 200' do
|
64
|
+
status_app(100 + rand(100)) { success? }
|
65
|
+
assert_body 'false'
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'is false for status > 299' do
|
69
|
+
status_app(300 + rand(300)) { success? }
|
70
|
+
assert_body 'false'
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe 'redirect?' do
|
75
|
+
it 'is true for 3xx status' do
|
76
|
+
status_app(300 + rand(100)) { redirect? }
|
77
|
+
assert_body 'true'
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'is false for status < 300' do
|
81
|
+
status_app(200 + rand(100)) { redirect? }
|
82
|
+
assert_body 'false'
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'is false for status > 399' do
|
86
|
+
status_app(400 + rand(200)) { redirect? }
|
87
|
+
assert_body 'false'
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe 'client_error?' do
|
92
|
+
it 'is true for 4xx status' do
|
93
|
+
status_app(400 + rand(100)) { client_error? }
|
94
|
+
assert_body 'true'
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'is false for status < 400' do
|
98
|
+
status_app(200 + rand(200)) { client_error? }
|
99
|
+
assert_body 'false'
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'is false for status > 499' do
|
103
|
+
status_app(500 + rand(100)) { client_error? }
|
104
|
+
assert_body 'false'
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe 'server_error?' do
|
109
|
+
it 'is true for 5xx status' do
|
110
|
+
status_app(500 + rand(100)) { server_error? }
|
111
|
+
assert_body 'true'
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'is false for status < 500' do
|
115
|
+
status_app(200 + rand(300)) { server_error? }
|
116
|
+
assert_body 'false'
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
24
120
|
describe 'body' do
|
25
121
|
it 'takes a block for defered body generation' do
|
26
122
|
mock_app {
|
@@ -57,7 +153,7 @@ class HelpersTest < Test::Unit::TestCase
|
|
57
153
|
get '/'
|
58
154
|
assert_equal 302, status
|
59
155
|
assert_equal '', body
|
60
|
-
assert_equal '/foo', response['Location']
|
156
|
+
assert_equal 'http://example.org/foo', response['Location']
|
61
157
|
end
|
62
158
|
|
63
159
|
it 'uses the code given when specified' do
|
@@ -71,7 +167,7 @@ class HelpersTest < Test::Unit::TestCase
|
|
71
167
|
get '/'
|
72
168
|
assert_equal 301, status
|
73
169
|
assert_equal '', body
|
74
|
-
assert_equal '/foo', response['Location']
|
170
|
+
assert_equal 'http://example.org/foo', response['Location']
|
75
171
|
end
|
76
172
|
|
77
173
|
it 'redirects back to request.referer when passed back' do
|
@@ -84,7 +180,87 @@ class HelpersTest < Test::Unit::TestCase
|
|
84
180
|
request = Rack::MockRequest.new(@app)
|
85
181
|
response = request.get('/try_redirect', 'HTTP_REFERER' => '/foo')
|
86
182
|
assert_equal 302, response.status
|
87
|
-
assert_equal '/foo', response['Location']
|
183
|
+
assert_equal 'http://example.org/foo', response['Location']
|
184
|
+
end
|
185
|
+
|
186
|
+
it 'redirects using a non-standard HTTP port' do
|
187
|
+
mock_app {
|
188
|
+
get '/' do
|
189
|
+
redirect '/foo'
|
190
|
+
end
|
191
|
+
}
|
192
|
+
|
193
|
+
request = Rack::MockRequest.new(@app)
|
194
|
+
response = request.get('/', 'SERVER_PORT' => '81')
|
195
|
+
assert_equal 'http://example.org:81/foo', response['Location']
|
196
|
+
end
|
197
|
+
|
198
|
+
it 'redirects using a non-standard HTTPS port' do
|
199
|
+
mock_app {
|
200
|
+
get '/' do
|
201
|
+
redirect '/foo'
|
202
|
+
end
|
203
|
+
}
|
204
|
+
|
205
|
+
request = Rack::MockRequest.new(@app)
|
206
|
+
response = request.get('/', 'SERVER_PORT' => '444')
|
207
|
+
assert_equal 'http://example.org:444/foo', response['Location']
|
208
|
+
end
|
209
|
+
|
210
|
+
it 'uses 303 for post requests if request is HTTP 1.1' do
|
211
|
+
mock_app { post('/') { redirect '/'} }
|
212
|
+
post '/', {}, 'HTTP_VERSION' => 'HTTP/1.1'
|
213
|
+
assert_equal 303, status
|
214
|
+
assert_equal '', body
|
215
|
+
assert_equal 'http://example.org/', response['Location']
|
216
|
+
end
|
217
|
+
|
218
|
+
it 'uses 302 for post requests if request is HTTP 1.0' do
|
219
|
+
mock_app { post('/') { redirect '/'} }
|
220
|
+
post '/', {}, 'HTTP_VERSION' => 'HTTP/1.0'
|
221
|
+
assert_equal 302, status
|
222
|
+
assert_equal '', body
|
223
|
+
assert_equal 'http://example.org/', response['Location']
|
224
|
+
end
|
225
|
+
|
226
|
+
it 'works behind a reverse proxy' do
|
227
|
+
mock_app do
|
228
|
+
get '/' do
|
229
|
+
redirect '/foo'
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
request = Rack::MockRequest.new(@app)
|
234
|
+
response = request.get('/', 'HTTP_X_FORWARDED_HOST' => 'example.com', 'SERVER_PORT' => '8080')
|
235
|
+
assert_equal 'http://example.com/foo', response['Location']
|
236
|
+
end
|
237
|
+
|
238
|
+
it 'accepts absolute URIs' do
|
239
|
+
mock_app do
|
240
|
+
get '/' do
|
241
|
+
redirect 'http://google.com'
|
242
|
+
fail 'redirect should halt'
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
get '/'
|
247
|
+
assert_equal 302, status
|
248
|
+
assert_equal '', body
|
249
|
+
assert_equal 'http://google.com', response['Location']
|
250
|
+
end
|
251
|
+
|
252
|
+
it 'accepts absolute URIs with a different schema' do
|
253
|
+
mock_app do
|
254
|
+
get '/' do
|
255
|
+
redirect 'mailto:jsmith@example.com'
|
256
|
+
fail 'redirect should halt'
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
get '/'
|
261
|
+
assert_equal 302, status
|
262
|
+
assert_equal '', body
|
263
|
+
assert_equal 'mailto:jsmith@example.com', response['Location']
|
88
264
|
end
|
89
265
|
end
|
90
266
|
|
@@ -204,7 +380,7 @@ class HelpersTest < Test::Unit::TestCase
|
|
204
380
|
enable :sessions
|
205
381
|
|
206
382
|
get '/' do
|
207
|
-
assert session.
|
383
|
+
assert session[:foo].nil?
|
208
384
|
session[:foo] = 'bar'
|
209
385
|
redirect '/hi'
|
210
386
|
end
|
@@ -218,6 +394,62 @@ class HelpersTest < Test::Unit::TestCase
|
|
218
394
|
follow_redirect!
|
219
395
|
assert_equal 'hi bar', body
|
220
396
|
end
|
397
|
+
|
398
|
+
it 'inserts session middleware' do
|
399
|
+
mock_app do
|
400
|
+
enable :sessions
|
401
|
+
get '/' do
|
402
|
+
assert env['rack.session']
|
403
|
+
assert env['rack.session.options']
|
404
|
+
'ok'
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
get '/'
|
409
|
+
assert_body 'ok'
|
410
|
+
end
|
411
|
+
|
412
|
+
it 'sets a default session secret' do
|
413
|
+
mock_app do
|
414
|
+
enable :sessions
|
415
|
+
get '/' do
|
416
|
+
secret = env['rack.session.options'][:secret]
|
417
|
+
assert secret
|
418
|
+
assert_equal secret, settings.session_secret
|
419
|
+
'ok'
|
420
|
+
end
|
421
|
+
end
|
422
|
+
|
423
|
+
get '/'
|
424
|
+
assert_body 'ok'
|
425
|
+
end
|
426
|
+
|
427
|
+
it 'allows disabling session secret' do
|
428
|
+
mock_app do
|
429
|
+
enable :sessions
|
430
|
+
disable :session_secret
|
431
|
+
get '/' do
|
432
|
+
assert !env['rack.session.options'].include?(:session_secret)
|
433
|
+
'ok'
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
get '/'
|
438
|
+
assert_body 'ok'
|
439
|
+
end
|
440
|
+
|
441
|
+
it 'accepts an options hash' do
|
442
|
+
mock_app do
|
443
|
+
set :sessions, :foo => :bar
|
444
|
+
get '/' do
|
445
|
+
assert_equal env['rack.session.options'][:foo], :bar
|
446
|
+
'ok'
|
447
|
+
end
|
448
|
+
end
|
449
|
+
|
450
|
+
get '/'
|
451
|
+
assert_body 'ok'
|
452
|
+
end
|
221
453
|
end
|
222
454
|
|
223
455
|
describe 'mime_type' do
|
@@ -266,21 +498,21 @@ class HelpersTest < Test::Unit::TestCase
|
|
266
498
|
}
|
267
499
|
|
268
500
|
get '/'
|
269
|
-
assert_equal 'text/plain', response['Content-Type']
|
501
|
+
assert_equal 'text/plain;charset=utf-8', response['Content-Type']
|
270
502
|
assert_equal 'Hello World', body
|
271
503
|
end
|
272
504
|
|
273
505
|
it 'takes media type parameters (like charset=)' do
|
274
506
|
mock_app {
|
275
507
|
get '/' do
|
276
|
-
content_type 'text/html', :charset => '
|
508
|
+
content_type 'text/html', :charset => 'latin1'
|
277
509
|
"<h1>Hello, World</h1>"
|
278
510
|
end
|
279
511
|
}
|
280
512
|
|
281
513
|
get '/'
|
282
514
|
assert ok?
|
283
|
-
assert_equal 'text/html;charset=
|
515
|
+
assert_equal 'text/html;charset=latin1', response['Content-Type']
|
284
516
|
assert_equal "<h1>Hello, World</h1>", body
|
285
517
|
end
|
286
518
|
|
@@ -309,6 +541,94 @@ class HelpersTest < Test::Unit::TestCase
|
|
309
541
|
|
310
542
|
assert_raise(RuntimeError) { get '/foo.xml' }
|
311
543
|
end
|
544
|
+
|
545
|
+
it 'only sets default charset for specific mime types' do
|
546
|
+
tests_ran = false
|
547
|
+
mock_app do
|
548
|
+
mime_type :foo, 'text/foo'
|
549
|
+
mime_type :bar, 'application/bar'
|
550
|
+
mime_type :baz, 'application/baz'
|
551
|
+
add_charset << mime_type(:baz)
|
552
|
+
get '/' do
|
553
|
+
assert_equal content_type(:txt), 'text/plain;charset=utf-8'
|
554
|
+
assert_equal content_type(:css), 'text/css;charset=utf-8'
|
555
|
+
assert_equal content_type(:html), 'text/html;charset=utf-8'
|
556
|
+
assert_equal content_type(:foo), 'text/foo;charset=utf-8'
|
557
|
+
assert_equal content_type(:xml), 'application/xml;charset=utf-8'
|
558
|
+
assert_equal content_type(:xhtml), 'application/xhtml+xml;charset=utf-8'
|
559
|
+
assert_equal content_type(:js), 'application/javascript;charset=utf-8'
|
560
|
+
assert_equal content_type(:json), 'application/json;charset=utf-8'
|
561
|
+
assert_equal content_type(:bar), 'application/bar'
|
562
|
+
assert_equal content_type(:png), 'image/png'
|
563
|
+
assert_equal content_type(:baz), 'application/baz;charset=utf-8'
|
564
|
+
tests_ran = true
|
565
|
+
"done"
|
566
|
+
end
|
567
|
+
end
|
568
|
+
get '/'
|
569
|
+
assert tests_ran
|
570
|
+
end
|
571
|
+
|
572
|
+
it 'handles already present params' do
|
573
|
+
mock_app do
|
574
|
+
get '/' do
|
575
|
+
content_type 'foo/bar;level=1', :charset => 'utf-8'
|
576
|
+
'ok'
|
577
|
+
end
|
578
|
+
end
|
579
|
+
get '/'
|
580
|
+
assert_equal 'foo/bar;level=1, charset=utf-8', response['Content-Type']
|
581
|
+
end
|
582
|
+
|
583
|
+
it 'does not add charset if present' do
|
584
|
+
mock_app do
|
585
|
+
get '/' do
|
586
|
+
content_type 'text/plain;charset=utf-16'
|
587
|
+
'ok'
|
588
|
+
end
|
589
|
+
end
|
590
|
+
get '/'
|
591
|
+
assert_equal 'text/plain;charset=utf-16', response['Content-Type']
|
592
|
+
end
|
593
|
+
end
|
594
|
+
|
595
|
+
describe 'attachment' do
|
596
|
+
def attachment_app(filename=nil)
|
597
|
+
mock_app {
|
598
|
+
get '/attachment' do
|
599
|
+
attachment filename
|
600
|
+
response.write("<sinatra></sinatra>")
|
601
|
+
end
|
602
|
+
}
|
603
|
+
end
|
604
|
+
|
605
|
+
it 'sets the Content-Type response header' do
|
606
|
+
attachment_app('test.xml')
|
607
|
+
get '/attachment'
|
608
|
+
assert_equal 'application/xml;charset=utf-8', response['Content-Type']
|
609
|
+
assert_equal '<sinatra></sinatra>', body
|
610
|
+
end
|
611
|
+
|
612
|
+
it 'sets the Content-Type response header without extname' do
|
613
|
+
attachment_app('test')
|
614
|
+
get '/attachment'
|
615
|
+
assert_equal 'text/html;charset=utf-8', response['Content-Type']
|
616
|
+
assert_equal '<sinatra></sinatra>', body
|
617
|
+
end
|
618
|
+
|
619
|
+
it 'sets the Content-Type response header without extname' do
|
620
|
+
mock_app do
|
621
|
+
get '/attachment' do
|
622
|
+
content_type :atom
|
623
|
+
attachment 'test.xml'
|
624
|
+
response.write("<sinatra></sinatra>")
|
625
|
+
end
|
626
|
+
end
|
627
|
+
get '/attachment'
|
628
|
+
assert_equal 'application/atom+xml', response['Content-Type']
|
629
|
+
assert_equal '<sinatra></sinatra>', body
|
630
|
+
end
|
631
|
+
|
312
632
|
end
|
313
633
|
|
314
634
|
describe 'send_file' do
|
@@ -341,7 +661,19 @@ class HelpersTest < Test::Unit::TestCase
|
|
341
661
|
it 'sets the Content-Type response header if a mime-type can be located' do
|
342
662
|
send_file_app
|
343
663
|
get '/file.txt'
|
344
|
-
assert_equal 'text/plain', response['Content-Type']
|
664
|
+
assert_equal 'text/plain;charset=utf-8', response['Content-Type']
|
665
|
+
end
|
666
|
+
|
667
|
+
it 'sets the Content-Type response header if type option is set to a file extesion' do
|
668
|
+
send_file_app :type => 'html'
|
669
|
+
get '/file.txt'
|
670
|
+
assert_equal 'text/html;charset=utf-8', response['Content-Type']
|
671
|
+
end
|
672
|
+
|
673
|
+
it 'sets the Content-Type response header if type option is set to a mime type' do
|
674
|
+
send_file_app :type => 'application/octet-stream'
|
675
|
+
get '/file.txt'
|
676
|
+
assert_equal 'application/octet-stream', response['Content-Type']
|
345
677
|
end
|
346
678
|
|
347
679
|
it 'sets the Content-Length response header' do
|
@@ -356,6 +688,13 @@ class HelpersTest < Test::Unit::TestCase
|
|
356
688
|
assert_equal File.mtime(@file).httpdate, response['Last-Modified']
|
357
689
|
end
|
358
690
|
|
691
|
+
it 'allows passing in a differen Last-Modified response header with :last_modified' do
|
692
|
+
time = Time.now
|
693
|
+
send_file_app :last_modified => time
|
694
|
+
get '/file.txt'
|
695
|
+
assert_equal time.httpdate, response['Last-Modified']
|
696
|
+
end
|
697
|
+
|
359
698
|
it "returns a 404 when not found" do
|
360
699
|
mock_app {
|
361
700
|
get '/' do
|
@@ -378,134 +717,912 @@ class HelpersTest < Test::Unit::TestCase
|
|
378
717
|
assert_equal 'attachment; filename="file.txt"', response['Content-Disposition']
|
379
718
|
end
|
380
719
|
|
720
|
+
it "sets the Content-Disposition header when :disposition set to 'inline'" do
|
721
|
+
send_file_app :disposition => 'inline'
|
722
|
+
get '/file.txt'
|
723
|
+
assert_equal 'inline', response['Content-Disposition']
|
724
|
+
end
|
725
|
+
|
381
726
|
it "sets the Content-Disposition header when :filename provided" do
|
382
727
|
send_file_app :filename => 'foo.txt'
|
383
728
|
get '/file.txt'
|
384
729
|
assert_equal 'attachment; filename="foo.txt"', response['Content-Disposition']
|
385
730
|
end
|
731
|
+
|
732
|
+
it 'allows setting a custom status code' do
|
733
|
+
send_file_app :status => 201
|
734
|
+
get '/file.txt'
|
735
|
+
assert_status 201
|
736
|
+
end
|
737
|
+
|
738
|
+
it "is able to send files with unkown mime type" do
|
739
|
+
@file = File.dirname(__FILE__) + '/file.foobar'
|
740
|
+
File.open(@file, 'wb') { |io| io.write('Hello World') }
|
741
|
+
send_file_app
|
742
|
+
get '/file.txt'
|
743
|
+
assert_equal 'application/octet-stream', response['Content-Type']
|
744
|
+
end
|
745
|
+
|
746
|
+
it "does not override Content-Type if already set and no explicit type is given" do
|
747
|
+
path = @file
|
748
|
+
mock_app do
|
749
|
+
get '/' do
|
750
|
+
content_type :png
|
751
|
+
send_file path
|
752
|
+
end
|
753
|
+
end
|
754
|
+
get '/'
|
755
|
+
assert_equal 'image/png', response['Content-Type']
|
756
|
+
end
|
757
|
+
|
758
|
+
it "does override Content-Type even if already set, if explicit type is given" do
|
759
|
+
path = @file
|
760
|
+
mock_app do
|
761
|
+
get '/' do
|
762
|
+
content_type :png
|
763
|
+
send_file path, :type => :gif
|
764
|
+
end
|
765
|
+
end
|
766
|
+
get '/'
|
767
|
+
assert_equal 'image/gif', response['Content-Type']
|
768
|
+
end
|
386
769
|
end
|
387
770
|
|
388
771
|
describe 'cache_control' do
|
389
772
|
setup do
|
390
|
-
mock_app
|
391
|
-
get '/' do
|
392
|
-
cache_control :public, :no_cache, :max_age => 60
|
773
|
+
mock_app do
|
774
|
+
get '/foo' do
|
775
|
+
cache_control :public, :no_cache, :max_age => 60.0
|
393
776
|
'Hello World'
|
394
777
|
end
|
395
|
-
|
778
|
+
|
779
|
+
get '/bar' do
|
780
|
+
cache_control :public, :no_cache
|
781
|
+
'Hello World'
|
782
|
+
end
|
783
|
+
end
|
396
784
|
end
|
397
785
|
|
398
786
|
it 'sets the Cache-Control header' do
|
399
|
-
get '/'
|
787
|
+
get '/foo'
|
400
788
|
assert_equal ['public', 'no-cache', 'max-age=60'], response['Cache-Control'].split(', ')
|
401
789
|
end
|
790
|
+
|
791
|
+
it 'last argument does not have to be a hash' do
|
792
|
+
get '/bar'
|
793
|
+
assert_equal ['public', 'no-cache'], response['Cache-Control'].split(', ')
|
794
|
+
end
|
402
795
|
end
|
403
796
|
|
404
797
|
describe 'expires' do
|
405
798
|
setup do
|
406
|
-
mock_app
|
407
|
-
get '/' do
|
799
|
+
mock_app do
|
800
|
+
get '/foo' do
|
408
801
|
expires 60, :public, :no_cache
|
409
802
|
'Hello World'
|
410
803
|
end
|
411
|
-
|
804
|
+
|
805
|
+
get '/bar' do
|
806
|
+
expires Time.now
|
807
|
+
end
|
808
|
+
|
809
|
+
get '/baz' do
|
810
|
+
expires Time.at(0)
|
811
|
+
end
|
812
|
+
|
813
|
+
get '/blah' do
|
814
|
+
obj = Object.new
|
815
|
+
def obj.method_missing(*a, &b) 60.send(*a, &b) end
|
816
|
+
def obj.is_a?(thing) 60.is_a?(thing) end
|
817
|
+
expires obj, :public, :no_cache
|
818
|
+
'Hello World'
|
819
|
+
end
|
820
|
+
|
821
|
+
get '/boom' do
|
822
|
+
expires '9999'
|
823
|
+
end
|
824
|
+
end
|
412
825
|
end
|
413
826
|
|
414
827
|
it 'sets the Cache-Control header' do
|
415
|
-
get '/'
|
828
|
+
get '/foo'
|
416
829
|
assert_equal ['public', 'no-cache', 'max-age=60'], response['Cache-Control'].split(', ')
|
417
830
|
end
|
418
831
|
|
419
832
|
it 'sets the Expires header' do
|
420
|
-
get '/'
|
833
|
+
get '/foo'
|
421
834
|
assert_not_nil response['Expires']
|
422
835
|
end
|
423
|
-
end
|
424
836
|
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
mock_app {
|
429
|
-
get '/' do
|
430
|
-
body { 'Hello World' }
|
431
|
-
last_modified now
|
432
|
-
'Boo!'
|
433
|
-
end
|
434
|
-
}
|
435
|
-
@now = now
|
837
|
+
it 'allows passing time objects' do
|
838
|
+
get '/bar'
|
839
|
+
assert_not_nil response['Expires']
|
436
840
|
end
|
437
841
|
|
438
|
-
it '
|
439
|
-
get '/'
|
440
|
-
assert_equal
|
842
|
+
it 'allows passing time objects' do
|
843
|
+
get '/baz'
|
844
|
+
assert_equal 'Thu, 01 Jan 1970 00:00:00 GMT', response['Expires']
|
441
845
|
end
|
442
846
|
|
443
|
-
it '
|
444
|
-
get '/'
|
445
|
-
assert_equal
|
446
|
-
assert_equal 'Boo!', body
|
847
|
+
it 'accepts values pretending to be a Numeric (like ActiveSupport::Duration)' do
|
848
|
+
get '/blah'
|
849
|
+
assert_equal ['public', 'no-cache', 'max-age=60'], response['Cache-Control'].split(', ')
|
447
850
|
end
|
448
851
|
|
449
|
-
it '
|
450
|
-
|
451
|
-
assert_equal 304, status
|
452
|
-
assert_equal '', body
|
852
|
+
it 'fails when Time.parse raises an ArgumentError' do
|
853
|
+
assert_raise(ArgumentError) { get '/boom' }
|
453
854
|
end
|
855
|
+
end
|
454
856
|
|
857
|
+
describe 'last_modified' do
|
455
858
|
it 'ignores nil' do
|
456
|
-
mock_app
|
859
|
+
mock_app do
|
457
860
|
get '/' do last_modified nil; 200; end
|
458
|
-
|
861
|
+
end
|
459
862
|
|
460
863
|
get '/'
|
461
864
|
assert ! response['Last-Modified']
|
462
865
|
end
|
463
|
-
end
|
464
866
|
|
465
|
-
|
466
|
-
|
467
|
-
mock_app {
|
867
|
+
it 'does not change a status other than 200' do
|
868
|
+
mock_app do
|
468
869
|
get '/' do
|
469
|
-
|
470
|
-
|
471
|
-
'
|
870
|
+
status 299
|
871
|
+
last_modified Time.at(0)
|
872
|
+
'ok'
|
472
873
|
end
|
473
|
-
|
474
|
-
end
|
874
|
+
end
|
475
875
|
|
476
|
-
|
477
|
-
|
478
|
-
|
876
|
+
get('/', {}, 'HTTP_IF_MODIFIED_SINCE' => 'Sun, 26 Sep 2030 23:43:52 GMT')
|
877
|
+
assert_status 299
|
878
|
+
assert_body 'ok'
|
479
879
|
end
|
480
880
|
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
881
|
+
[Time.now, DateTime.now, Date.today, Time.now.to_i,
|
882
|
+
Struct.new(:to_time).new(Time.now) ].each do |last_modified_time|
|
883
|
+
describe "with #{last_modified_time.class.name}" do
|
884
|
+
setup do
|
885
|
+
mock_app do
|
886
|
+
get '/' do
|
887
|
+
last_modified last_modified_time
|
888
|
+
'Boo!'
|
889
|
+
end
|
890
|
+
end
|
891
|
+
wrapper = Object.new.extend Sinatra::Helpers
|
892
|
+
@last_modified_time = wrapper.time_for last_modified_time
|
893
|
+
end
|
486
894
|
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
895
|
+
# fixes strange missing test error when running complete test suite.
|
896
|
+
it("does not complain about missing tests") { }
|
897
|
+
|
898
|
+
context "when there's no If-Modified-Since header" do
|
899
|
+
it 'sets the Last-Modified header to a valid RFC 2616 date value' do
|
900
|
+
get '/'
|
901
|
+
assert_equal @last_modified_time.httpdate, response['Last-Modified']
|
902
|
+
end
|
903
|
+
|
904
|
+
it 'conditional GET misses and returns a body' do
|
905
|
+
get '/'
|
906
|
+
assert_equal 200, status
|
907
|
+
assert_equal 'Boo!', body
|
908
|
+
end
|
909
|
+
end
|
910
|
+
|
911
|
+
context "when there's an invalid If-Modified-Since header" do
|
912
|
+
it 'sets the Last-Modified header to a valid RFC 2616 date value' do
|
913
|
+
get '/', {}, { 'HTTP_IF_MODIFIED_SINCE' => 'a really weird date' }
|
914
|
+
assert_equal @last_modified_time.httpdate, response['Last-Modified']
|
915
|
+
end
|
916
|
+
|
917
|
+
it 'conditional GET misses and returns a body' do
|
918
|
+
get '/', {}, { 'HTTP_IF_MODIFIED_SINCE' => 'a really weird date' }
|
919
|
+
assert_equal 200, status
|
920
|
+
assert_equal 'Boo!', body
|
921
|
+
end
|
922
|
+
end
|
923
|
+
|
924
|
+
context "when the resource has been modified since the If-Modified-Since header date" do
|
925
|
+
it 'sets the Last-Modified header to a valid RFC 2616 date value' do
|
926
|
+
get '/', {}, { 'HTTP_IF_MODIFIED_SINCE' => (@last_modified_time - 1).httpdate }
|
927
|
+
assert_equal @last_modified_time.httpdate, response['Last-Modified']
|
928
|
+
end
|
929
|
+
|
930
|
+
it 'conditional GET misses and returns a body' do
|
931
|
+
get '/', {}, { 'HTTP_IF_MODIFIED_SINCE' => (@last_modified_time - 1).httpdate }
|
932
|
+
assert_equal 200, status
|
933
|
+
assert_equal 'Boo!', body
|
934
|
+
end
|
935
|
+
|
936
|
+
it 'does not rely on string comparison' do
|
937
|
+
mock_app do
|
938
|
+
get '/compare' do
|
939
|
+
last_modified "Mon, 18 Oct 2010 20:57:11 GMT"
|
940
|
+
"foo"
|
941
|
+
end
|
942
|
+
end
|
943
|
+
|
944
|
+
get '/compare', {}, { 'HTTP_IF_MODIFIED_SINCE' => 'Sun, 26 Sep 2010 23:43:52 GMT' }
|
945
|
+
assert_equal 200, status
|
946
|
+
assert_equal 'foo', body
|
947
|
+
get '/compare', {}, { 'HTTP_IF_MODIFIED_SINCE' => 'Sun, 26 Sep 2030 23:43:52 GMT' }
|
948
|
+
assert_equal 304, status
|
949
|
+
assert_equal '', body
|
950
|
+
end
|
951
|
+
end
|
952
|
+
|
953
|
+
context "when the resource has been modified on the exact If-Modified-Since header date" do
|
954
|
+
it 'sets the Last-Modified header to a valid RFC 2616 date value' do
|
955
|
+
get '/', {}, { 'HTTP_IF_MODIFIED_SINCE' => @last_modified_time.httpdate }
|
956
|
+
assert_equal @last_modified_time.httpdate, response['Last-Modified']
|
957
|
+
end
|
958
|
+
|
959
|
+
it 'conditional GET matches and halts' do
|
960
|
+
get '/', {}, { 'HTTP_IF_MODIFIED_SINCE' => @last_modified_time.httpdate }
|
961
|
+
assert_equal 304, status
|
962
|
+
assert_equal '', body
|
963
|
+
end
|
964
|
+
end
|
965
|
+
|
966
|
+
context "when the resource hasn't been modified since the If-Modified-Since header date" do
|
967
|
+
it 'sets the Last-Modified header to a valid RFC 2616 date value' do
|
968
|
+
get '/', {}, { 'HTTP_IF_MODIFIED_SINCE' => (@last_modified_time + 1).httpdate }
|
969
|
+
assert_equal @last_modified_time.httpdate, response['Last-Modified']
|
970
|
+
end
|
971
|
+
|
972
|
+
it 'conditional GET matches and halts' do
|
973
|
+
get '/', {}, { 'HTTP_IF_MODIFIED_SINCE' => (@last_modified_time + 1).httpdate }
|
974
|
+
assert_equal 304, status
|
975
|
+
assert_equal '', body
|
976
|
+
end
|
977
|
+
end
|
978
|
+
|
979
|
+
context "If-Unmodified-Since" do
|
980
|
+
it 'results in 200 if resource has not been modified' do
|
981
|
+
get '/', {}, { 'HTTP_IF_UNMODIFIED_SINCE' => 'Sun, 26 Sep 2030 23:43:52 GMT' }
|
982
|
+
assert_equal 200, status
|
983
|
+
assert_equal 'Boo!', body
|
984
|
+
end
|
985
|
+
|
986
|
+
it 'results in 412 if resource has been modified' do
|
987
|
+
get '/', {}, { 'HTTP_IF_UNMODIFIED_SINCE' => Time.at(0).httpdate }
|
988
|
+
assert_equal 412, status
|
989
|
+
assert_equal '', body
|
990
|
+
end
|
991
|
+
end
|
992
|
+
end
|
491
993
|
end
|
994
|
+
end
|
492
995
|
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
996
|
+
describe 'etag' do
|
997
|
+
context "safe requests" do
|
998
|
+
it 'returns 200 for normal requests' do
|
999
|
+
mock_app do
|
1000
|
+
get '/' do
|
1001
|
+
etag 'foo'
|
1002
|
+
'ok'
|
1003
|
+
end
|
1004
|
+
end
|
1005
|
+
|
1006
|
+
get('/')
|
1007
|
+
assert_status 200
|
1008
|
+
assert_body 'ok'
|
1009
|
+
end
|
1010
|
+
|
1011
|
+
context "If-None-Match" do
|
1012
|
+
it 'returns 304 when If-None-Match is *' do
|
1013
|
+
mock_app do
|
1014
|
+
get '/' do
|
1015
|
+
etag 'foo'
|
1016
|
+
'ok'
|
1017
|
+
end
|
1018
|
+
end
|
1019
|
+
|
1020
|
+
get('/', {}, 'HTTP_IF_NONE_MATCH' => '*')
|
1021
|
+
assert_status 304
|
1022
|
+
assert_body ''
|
1023
|
+
end
|
1024
|
+
|
1025
|
+
it 'returns 200 when If-None-Match is * for new resources' do
|
1026
|
+
mock_app do
|
1027
|
+
get '/' do
|
1028
|
+
etag 'foo', :new_resource => true
|
1029
|
+
'ok'
|
1030
|
+
end
|
1031
|
+
end
|
1032
|
+
|
1033
|
+
get('/', {}, 'HTTP_IF_NONE_MATCH' => '*')
|
1034
|
+
assert_status 200
|
1035
|
+
assert_body 'ok'
|
1036
|
+
end
|
1037
|
+
|
1038
|
+
it 'returns 304 when If-None-Match is * for existing resources' do
|
1039
|
+
mock_app do
|
1040
|
+
get '/' do
|
1041
|
+
etag 'foo', :new_resource => false
|
1042
|
+
'ok'
|
1043
|
+
end
|
1044
|
+
end
|
1045
|
+
|
1046
|
+
get('/', {}, 'HTTP_IF_NONE_MATCH' => '*')
|
1047
|
+
assert_status 304
|
1048
|
+
assert_body ''
|
1049
|
+
end
|
1050
|
+
|
1051
|
+
it 'returns 304 when If-None-Match is the etag' do
|
1052
|
+
mock_app do
|
1053
|
+
get '/' do
|
1054
|
+
etag 'foo'
|
1055
|
+
'ok'
|
1056
|
+
end
|
1057
|
+
end
|
1058
|
+
|
1059
|
+
get('/', {}, 'HTTP_IF_NONE_MATCH' => '"foo"')
|
1060
|
+
assert_status 304
|
1061
|
+
assert_body ''
|
1062
|
+
end
|
1063
|
+
|
1064
|
+
it 'returns 304 when If-None-Match includes the etag' do
|
1065
|
+
mock_app do
|
1066
|
+
get '/' do
|
1067
|
+
etag 'foo'
|
1068
|
+
'ok'
|
1069
|
+
end
|
1070
|
+
end
|
1071
|
+
|
1072
|
+
get('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar", "foo"')
|
1073
|
+
assert_status 304
|
1074
|
+
assert_body ''
|
1075
|
+
end
|
1076
|
+
|
1077
|
+
it 'returns 200 when If-None-Match does not include the etag' do
|
1078
|
+
mock_app do
|
1079
|
+
get '/' do
|
1080
|
+
etag 'foo'
|
1081
|
+
'ok'
|
1082
|
+
end
|
1083
|
+
end
|
1084
|
+
|
1085
|
+
get('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar"')
|
1086
|
+
assert_status 200
|
1087
|
+
assert_body 'ok'
|
1088
|
+
end
|
1089
|
+
|
1090
|
+
it 'ignores If-Modified-Since if If-None-Match does not match' do
|
1091
|
+
mock_app do
|
1092
|
+
get '/' do
|
1093
|
+
etag 'foo'
|
1094
|
+
last_modified Time.at(0)
|
1095
|
+
'ok'
|
1096
|
+
end
|
1097
|
+
end
|
1098
|
+
|
1099
|
+
get('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar"')
|
1100
|
+
assert_status 200
|
1101
|
+
assert_body 'ok'
|
1102
|
+
end
|
1103
|
+
|
1104
|
+
it 'does not change a status code other than 2xx or 304' do
|
1105
|
+
mock_app do
|
1106
|
+
get '/' do
|
1107
|
+
status 499
|
1108
|
+
etag 'foo'
|
1109
|
+
'ok'
|
1110
|
+
end
|
1111
|
+
end
|
1112
|
+
|
1113
|
+
get('/', {}, 'HTTP_IF_NONE_MATCH' => '"foo"')
|
1114
|
+
assert_status 499
|
1115
|
+
assert_body 'ok'
|
1116
|
+
end
|
1117
|
+
|
1118
|
+
it 'does change 2xx status codes' do
|
1119
|
+
mock_app do
|
1120
|
+
get '/' do
|
1121
|
+
status 299
|
1122
|
+
etag 'foo'
|
1123
|
+
'ok'
|
1124
|
+
end
|
1125
|
+
end
|
1126
|
+
|
1127
|
+
get('/', {}, 'HTTP_IF_NONE_MATCH' => '"foo"')
|
1128
|
+
assert_status 304
|
1129
|
+
assert_body ''
|
1130
|
+
end
|
1131
|
+
|
1132
|
+
it 'does not send a body on 304 status codes' do
|
1133
|
+
mock_app do
|
1134
|
+
get '/' do
|
1135
|
+
status 304
|
1136
|
+
etag 'foo'
|
1137
|
+
'ok'
|
1138
|
+
end
|
1139
|
+
end
|
1140
|
+
|
1141
|
+
get('/', {}, 'HTTP_IF_NONE_MATCH' => '"foo"')
|
1142
|
+
assert_status 304
|
1143
|
+
assert_body ''
|
1144
|
+
end
|
1145
|
+
end
|
1146
|
+
|
1147
|
+
context "If-Match" do
|
1148
|
+
it 'returns 200 when If-Match is the etag' do
|
1149
|
+
mock_app do
|
1150
|
+
get '/' do
|
1151
|
+
etag 'foo'
|
1152
|
+
'ok'
|
1153
|
+
end
|
1154
|
+
end
|
1155
|
+
|
1156
|
+
get('/', {}, 'HTTP_IF_MATCH' => '"foo"')
|
1157
|
+
assert_status 200
|
1158
|
+
assert_body 'ok'
|
1159
|
+
end
|
1160
|
+
|
1161
|
+
it 'returns 200 when If-Match includes the etag' do
|
1162
|
+
mock_app do
|
1163
|
+
get '/' do
|
1164
|
+
etag 'foo'
|
1165
|
+
'ok'
|
1166
|
+
end
|
1167
|
+
end
|
1168
|
+
|
1169
|
+
get('/', {}, 'HTTP_IF_MATCH' => '"foo", "bar"')
|
1170
|
+
assert_status 200
|
1171
|
+
assert_body 'ok'
|
1172
|
+
end
|
1173
|
+
|
1174
|
+
it 'returns 200 when If-Match is *' do
|
1175
|
+
mock_app do
|
1176
|
+
get '/' do
|
1177
|
+
etag 'foo'
|
1178
|
+
'ok'
|
1179
|
+
end
|
1180
|
+
end
|
1181
|
+
|
1182
|
+
get('/', {}, 'HTTP_IF_MATCH' => '*')
|
1183
|
+
assert_status 200
|
1184
|
+
assert_body 'ok'
|
1185
|
+
end
|
1186
|
+
|
1187
|
+
it 'returns 412 when If-Match is * for new resources' do
|
1188
|
+
mock_app do
|
1189
|
+
get '/' do
|
1190
|
+
etag 'foo', :new_resource => true
|
1191
|
+
'ok'
|
1192
|
+
end
|
1193
|
+
end
|
1194
|
+
|
1195
|
+
get('/', {}, 'HTTP_IF_MATCH' => '*')
|
1196
|
+
assert_status 412
|
1197
|
+
assert_body ''
|
1198
|
+
end
|
1199
|
+
|
1200
|
+
it 'returns 200 when If-Match is * for existing resources' do
|
1201
|
+
mock_app do
|
1202
|
+
get '/' do
|
1203
|
+
etag 'foo', :new_resource => false
|
1204
|
+
'ok'
|
1205
|
+
end
|
1206
|
+
end
|
1207
|
+
|
1208
|
+
get('/', {}, 'HTTP_IF_MATCH' => '*')
|
1209
|
+
assert_status 200
|
1210
|
+
assert_body 'ok'
|
1211
|
+
end
|
1212
|
+
|
1213
|
+
it 'returns 412 when If-Match does not include the etag' do
|
1214
|
+
mock_app do
|
1215
|
+
get '/' do
|
1216
|
+
etag 'foo'
|
1217
|
+
'ok'
|
1218
|
+
end
|
1219
|
+
end
|
1220
|
+
|
1221
|
+
get('/', {}, 'HTTP_IF_MATCH' => '"bar"')
|
1222
|
+
assert_status 412
|
1223
|
+
assert_body ''
|
1224
|
+
end
|
1225
|
+
end
|
1226
|
+
end
|
1227
|
+
|
1228
|
+
context "idempotent requests" do
|
1229
|
+
it 'returns 200 for normal requests' do
|
1230
|
+
mock_app do
|
1231
|
+
put '/' do
|
1232
|
+
etag 'foo'
|
1233
|
+
'ok'
|
1234
|
+
end
|
1235
|
+
end
|
1236
|
+
|
1237
|
+
put('/')
|
1238
|
+
assert_status 200
|
1239
|
+
assert_body 'ok'
|
1240
|
+
end
|
1241
|
+
|
1242
|
+
context "If-None-Match" do
|
1243
|
+
it 'returns 412 when If-None-Match is *' do
|
1244
|
+
mock_app do
|
1245
|
+
put '/' do
|
1246
|
+
etag 'foo'
|
1247
|
+
'ok'
|
1248
|
+
end
|
1249
|
+
end
|
1250
|
+
|
1251
|
+
put('/', {}, 'HTTP_IF_NONE_MATCH' => '*')
|
1252
|
+
assert_status 412
|
1253
|
+
assert_body ''
|
1254
|
+
end
|
1255
|
+
|
1256
|
+
it 'returns 200 when If-None-Match is * for new resources' do
|
1257
|
+
mock_app do
|
1258
|
+
put '/' do
|
1259
|
+
etag 'foo', :new_resource => true
|
1260
|
+
'ok'
|
1261
|
+
end
|
1262
|
+
end
|
1263
|
+
|
1264
|
+
put('/', {}, 'HTTP_IF_NONE_MATCH' => '*')
|
1265
|
+
assert_status 200
|
1266
|
+
assert_body 'ok'
|
1267
|
+
end
|
1268
|
+
|
1269
|
+
it 'returns 412 when If-None-Match is * for existing resources' do
|
1270
|
+
mock_app do
|
1271
|
+
put '/' do
|
1272
|
+
etag 'foo', :new_resource => false
|
1273
|
+
'ok'
|
1274
|
+
end
|
1275
|
+
end
|
1276
|
+
|
1277
|
+
put('/', {}, 'HTTP_IF_NONE_MATCH' => '*')
|
1278
|
+
assert_status 412
|
1279
|
+
assert_body ''
|
1280
|
+
end
|
1281
|
+
|
1282
|
+
it 'returns 412 when If-None-Match is the etag' do
|
1283
|
+
mock_app do
|
1284
|
+
put '/' do
|
1285
|
+
etag 'foo'
|
1286
|
+
'ok'
|
1287
|
+
end
|
1288
|
+
end
|
1289
|
+
|
1290
|
+
put('/', {}, 'HTTP_IF_NONE_MATCH' => '"foo"')
|
1291
|
+
assert_status 412
|
1292
|
+
assert_body ''
|
1293
|
+
end
|
1294
|
+
|
1295
|
+
it 'returns 412 when If-None-Match includes the etag' do
|
1296
|
+
mock_app do
|
1297
|
+
put '/' do
|
1298
|
+
etag 'foo'
|
1299
|
+
'ok'
|
1300
|
+
end
|
1301
|
+
end
|
1302
|
+
|
1303
|
+
put('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar", "foo"')
|
1304
|
+
assert_status 412
|
1305
|
+
assert_body ''
|
1306
|
+
end
|
1307
|
+
|
1308
|
+
it 'returns 200 when If-None-Match does not include the etag' do
|
1309
|
+
mock_app do
|
1310
|
+
put '/' do
|
1311
|
+
etag 'foo'
|
1312
|
+
'ok'
|
1313
|
+
end
|
1314
|
+
end
|
1315
|
+
|
1316
|
+
put('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar"')
|
1317
|
+
assert_status 200
|
1318
|
+
assert_body 'ok'
|
1319
|
+
end
|
1320
|
+
|
1321
|
+
it 'ignores If-Modified-Since if If-None-Match does not match' do
|
1322
|
+
mock_app do
|
1323
|
+
put '/' do
|
1324
|
+
etag 'foo'
|
1325
|
+
last_modified Time.at(0)
|
1326
|
+
'ok'
|
1327
|
+
end
|
1328
|
+
end
|
1329
|
+
|
1330
|
+
put('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar"')
|
1331
|
+
assert_status 200
|
1332
|
+
assert_body 'ok'
|
1333
|
+
end
|
1334
|
+
end
|
1335
|
+
|
1336
|
+
context "If-Match" do
|
1337
|
+
it 'returns 200 when If-Match is the etag' do
|
1338
|
+
mock_app do
|
1339
|
+
put '/' do
|
1340
|
+
etag 'foo'
|
1341
|
+
'ok'
|
1342
|
+
end
|
1343
|
+
end
|
1344
|
+
|
1345
|
+
put('/', {}, 'HTTP_IF_MATCH' => '"foo"')
|
1346
|
+
assert_status 200
|
1347
|
+
assert_body 'ok'
|
1348
|
+
end
|
1349
|
+
|
1350
|
+
it 'returns 200 when If-Match includes the etag' do
|
1351
|
+
mock_app do
|
1352
|
+
put '/' do
|
1353
|
+
etag 'foo'
|
1354
|
+
'ok'
|
1355
|
+
end
|
1356
|
+
end
|
1357
|
+
|
1358
|
+
put('/', {}, 'HTTP_IF_MATCH' => '"foo", "bar"')
|
1359
|
+
assert_status 200
|
1360
|
+
assert_body 'ok'
|
1361
|
+
end
|
1362
|
+
|
1363
|
+
it 'returns 200 when If-Match is *' do
|
1364
|
+
mock_app do
|
1365
|
+
put '/' do
|
1366
|
+
etag 'foo'
|
1367
|
+
'ok'
|
1368
|
+
end
|
1369
|
+
end
|
1370
|
+
|
1371
|
+
put('/', {}, 'HTTP_IF_MATCH' => '*')
|
1372
|
+
assert_status 200
|
1373
|
+
assert_body 'ok'
|
1374
|
+
end
|
1375
|
+
|
1376
|
+
it 'returns 412 when If-Match is * for new resources' do
|
1377
|
+
mock_app do
|
1378
|
+
put '/' do
|
1379
|
+
etag 'foo', :new_resource => true
|
1380
|
+
'ok'
|
1381
|
+
end
|
1382
|
+
end
|
1383
|
+
|
1384
|
+
put('/', {}, 'HTTP_IF_MATCH' => '*')
|
1385
|
+
assert_status 412
|
1386
|
+
assert_body ''
|
1387
|
+
end
|
1388
|
+
|
1389
|
+
it 'returns 200 when If-Match is * for existing resources' do
|
1390
|
+
mock_app do
|
1391
|
+
put '/' do
|
1392
|
+
etag 'foo', :new_resource => false
|
1393
|
+
'ok'
|
1394
|
+
end
|
1395
|
+
end
|
1396
|
+
|
1397
|
+
put('/', {}, 'HTTP_IF_MATCH' => '*')
|
1398
|
+
assert_status 200
|
1399
|
+
assert_body 'ok'
|
1400
|
+
end
|
1401
|
+
|
1402
|
+
it 'returns 412 when If-Match does not include the etag' do
|
1403
|
+
mock_app do
|
1404
|
+
put '/' do
|
1405
|
+
etag 'foo'
|
1406
|
+
'ok'
|
1407
|
+
end
|
1408
|
+
end
|
1409
|
+
|
1410
|
+
put('/', {}, 'HTTP_IF_MATCH' => '"bar"')
|
1411
|
+
assert_status 412
|
1412
|
+
assert_body ''
|
1413
|
+
end
|
1414
|
+
end
|
1415
|
+
end
|
1416
|
+
|
1417
|
+
context "post requests" do
|
1418
|
+
it 'returns 200 for normal requests' do
|
1419
|
+
mock_app do
|
1420
|
+
post '/' do
|
1421
|
+
etag 'foo'
|
1422
|
+
'ok'
|
1423
|
+
end
|
1424
|
+
end
|
1425
|
+
|
1426
|
+
post('/')
|
1427
|
+
assert_status 200
|
1428
|
+
assert_body 'ok'
|
1429
|
+
end
|
1430
|
+
|
1431
|
+
context "If-None-Match" do
|
1432
|
+
it 'returns 200 when If-None-Match is *' do
|
1433
|
+
mock_app do
|
1434
|
+
post '/' do
|
1435
|
+
etag 'foo'
|
1436
|
+
'ok'
|
1437
|
+
end
|
1438
|
+
end
|
1439
|
+
|
1440
|
+
post('/', {}, 'HTTP_IF_NONE_MATCH' => '*')
|
1441
|
+
assert_status 200
|
1442
|
+
assert_body 'ok'
|
1443
|
+
end
|
1444
|
+
|
1445
|
+
it 'returns 200 when If-None-Match is * for new resources' do
|
1446
|
+
mock_app do
|
1447
|
+
post '/' do
|
1448
|
+
etag 'foo', :new_resource => true
|
1449
|
+
'ok'
|
1450
|
+
end
|
1451
|
+
end
|
1452
|
+
|
1453
|
+
post('/', {}, 'HTTP_IF_NONE_MATCH' => '*')
|
1454
|
+
assert_status 200
|
1455
|
+
assert_body 'ok'
|
1456
|
+
end
|
1457
|
+
|
1458
|
+
it 'returns 412 when If-None-Match is * for existing resources' do
|
1459
|
+
mock_app do
|
1460
|
+
post '/' do
|
1461
|
+
etag 'foo', :new_resource => false
|
1462
|
+
'ok'
|
1463
|
+
end
|
1464
|
+
end
|
1465
|
+
|
1466
|
+
post('/', {}, 'HTTP_IF_NONE_MATCH' => '*')
|
1467
|
+
assert_status 412
|
1468
|
+
assert_body ''
|
1469
|
+
end
|
1470
|
+
|
1471
|
+
it 'returns 412 when If-None-Match is the etag' do
|
1472
|
+
mock_app do
|
1473
|
+
post '/' do
|
1474
|
+
etag 'foo'
|
1475
|
+
'ok'
|
1476
|
+
end
|
1477
|
+
end
|
1478
|
+
|
1479
|
+
post('/', {}, 'HTTP_IF_NONE_MATCH' => '"foo"')
|
1480
|
+
assert_status 412
|
1481
|
+
assert_body ''
|
1482
|
+
end
|
1483
|
+
|
1484
|
+
it 'returns 412 when If-None-Match includes the etag' do
|
1485
|
+
mock_app do
|
1486
|
+
post '/' do
|
1487
|
+
etag 'foo'
|
1488
|
+
'ok'
|
1489
|
+
end
|
1490
|
+
end
|
1491
|
+
|
1492
|
+
post('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar", "foo"')
|
1493
|
+
assert_status 412
|
1494
|
+
assert_body ''
|
1495
|
+
end
|
1496
|
+
|
1497
|
+
it 'returns 200 when If-None-Match does not include the etag' do
|
1498
|
+
mock_app do
|
1499
|
+
post '/' do
|
1500
|
+
etag 'foo'
|
1501
|
+
'ok'
|
1502
|
+
end
|
1503
|
+
end
|
1504
|
+
|
1505
|
+
post('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar"')
|
1506
|
+
assert_status 200
|
1507
|
+
assert_body 'ok'
|
1508
|
+
end
|
1509
|
+
|
1510
|
+
it 'ignores If-Modified-Since if If-None-Match does not match' do
|
1511
|
+
mock_app do
|
1512
|
+
post '/' do
|
1513
|
+
etag 'foo'
|
1514
|
+
last_modified Time.at(0)
|
1515
|
+
'ok'
|
1516
|
+
end
|
1517
|
+
end
|
1518
|
+
|
1519
|
+
post('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar"')
|
1520
|
+
assert_status 200
|
1521
|
+
assert_body 'ok'
|
1522
|
+
end
|
1523
|
+
end
|
1524
|
+
|
1525
|
+
context "If-Match" do
|
1526
|
+
it 'returns 200 when If-Match is the etag' do
|
1527
|
+
mock_app do
|
1528
|
+
post '/' do
|
1529
|
+
etag 'foo'
|
1530
|
+
'ok'
|
1531
|
+
end
|
1532
|
+
end
|
1533
|
+
|
1534
|
+
post('/', {}, 'HTTP_IF_MATCH' => '"foo"')
|
1535
|
+
assert_status 200
|
1536
|
+
assert_body 'ok'
|
1537
|
+
end
|
1538
|
+
|
1539
|
+
it 'returns 200 when If-Match includes the etag' do
|
1540
|
+
mock_app do
|
1541
|
+
post '/' do
|
1542
|
+
etag 'foo'
|
1543
|
+
'ok'
|
1544
|
+
end
|
1545
|
+
end
|
1546
|
+
|
1547
|
+
post('/', {}, 'HTTP_IF_MATCH' => '"foo", "bar"')
|
1548
|
+
assert_status 200
|
1549
|
+
assert_body 'ok'
|
1550
|
+
end
|
1551
|
+
|
1552
|
+
it 'returns 412 when If-Match is *' do
|
1553
|
+
mock_app do
|
1554
|
+
post '/' do
|
1555
|
+
etag 'foo'
|
1556
|
+
'ok'
|
1557
|
+
end
|
1558
|
+
end
|
1559
|
+
|
1560
|
+
post('/', {}, 'HTTP_IF_MATCH' => '*')
|
1561
|
+
assert_status 412
|
1562
|
+
assert_body ''
|
1563
|
+
end
|
1564
|
+
|
1565
|
+
it 'returns 412 when If-Match is * for new resources' do
|
1566
|
+
mock_app do
|
1567
|
+
post '/' do
|
1568
|
+
etag 'foo', :new_resource => true
|
1569
|
+
'ok'
|
1570
|
+
end
|
1571
|
+
end
|
1572
|
+
|
1573
|
+
post('/', {}, 'HTTP_IF_MATCH' => '*')
|
1574
|
+
assert_status 412
|
1575
|
+
assert_body ''
|
1576
|
+
end
|
1577
|
+
|
1578
|
+
it 'returns 200 when If-Match is * for existing resources' do
|
1579
|
+
mock_app do
|
1580
|
+
post '/' do
|
1581
|
+
etag 'foo', :new_resource => false
|
1582
|
+
'ok'
|
1583
|
+
end
|
1584
|
+
end
|
1585
|
+
|
1586
|
+
post('/', {}, 'HTTP_IF_MATCH' => '*')
|
1587
|
+
assert_status 200
|
1588
|
+
assert_body 'ok'
|
1589
|
+
end
|
1590
|
+
|
1591
|
+
it 'returns 412 when If-Match does not include the etag' do
|
1592
|
+
mock_app do
|
1593
|
+
post '/' do
|
1594
|
+
etag 'foo'
|
1595
|
+
'ok'
|
1596
|
+
end
|
1597
|
+
end
|
1598
|
+
|
1599
|
+
post('/', {}, 'HTTP_IF_MATCH' => '"bar"')
|
1600
|
+
assert_status 412
|
1601
|
+
assert_body ''
|
1602
|
+
end
|
1603
|
+
end
|
497
1604
|
end
|
498
1605
|
|
499
1606
|
it 'uses a weak etag with the :weak option' do
|
500
|
-
mock_app
|
1607
|
+
mock_app do
|
501
1608
|
get '/' do
|
502
1609
|
etag 'FOO', :weak
|
503
1610
|
"that's weak, dude."
|
504
1611
|
end
|
505
|
-
|
1612
|
+
end
|
506
1613
|
get '/'
|
507
1614
|
assert_equal 'W/"FOO"', response['ETag']
|
508
1615
|
end
|
1616
|
+
|
1617
|
+
it 'raises an ArgumentError for an invalid strength' do
|
1618
|
+
mock_app do
|
1619
|
+
get '/' do
|
1620
|
+
etag 'FOO', :w00t
|
1621
|
+
"that's weak, dude."
|
1622
|
+
end
|
1623
|
+
end
|
1624
|
+
assert_raise(ArgumentError) { get '/' }
|
1625
|
+
end
|
509
1626
|
end
|
510
1627
|
|
511
1628
|
describe 'back' do
|
@@ -522,6 +1639,96 @@ class HelpersTest < Test::Unit::TestCase
|
|
522
1639
|
end
|
523
1640
|
end
|
524
1641
|
|
1642
|
+
describe 'uri' do
|
1643
|
+
it 'generates absolute urls' do
|
1644
|
+
mock_app { get('/') { uri }}
|
1645
|
+
get '/'
|
1646
|
+
assert_equal 'http://example.org/', body
|
1647
|
+
end
|
1648
|
+
|
1649
|
+
it 'includes path_info' do
|
1650
|
+
mock_app { get('/:name') { uri }}
|
1651
|
+
get '/foo'
|
1652
|
+
assert_equal 'http://example.org/foo', body
|
1653
|
+
end
|
1654
|
+
|
1655
|
+
it 'allows passing an alternative to path_info' do
|
1656
|
+
mock_app { get('/:name') { uri '/bar' }}
|
1657
|
+
get '/foo'
|
1658
|
+
assert_equal 'http://example.org/bar', body
|
1659
|
+
end
|
1660
|
+
|
1661
|
+
it 'includes script_name' do
|
1662
|
+
mock_app { get('/:name') { uri '/bar' }}
|
1663
|
+
get '/foo', {}, { "SCRIPT_NAME" => '/foo' }
|
1664
|
+
assert_equal 'http://example.org/foo/bar', body
|
1665
|
+
end
|
1666
|
+
|
1667
|
+
it 'handles absolute URIs' do
|
1668
|
+
mock_app { get('/') { uri 'http://google.com' }}
|
1669
|
+
get '/'
|
1670
|
+
assert_equal 'http://google.com', body
|
1671
|
+
end
|
1672
|
+
|
1673
|
+
it 'handles different protocols' do
|
1674
|
+
mock_app { get('/') { uri 'mailto:jsmith@example.com' }}
|
1675
|
+
get '/'
|
1676
|
+
assert_equal 'mailto:jsmith@example.com', body
|
1677
|
+
end
|
1678
|
+
|
1679
|
+
it 'is aliased to #url' do
|
1680
|
+
mock_app { get('/') { url }}
|
1681
|
+
get '/'
|
1682
|
+
assert_equal 'http://example.org/', body
|
1683
|
+
end
|
1684
|
+
|
1685
|
+
it 'is aliased to #to' do
|
1686
|
+
mock_app { get('/') { to }}
|
1687
|
+
get '/'
|
1688
|
+
assert_equal 'http://example.org/', body
|
1689
|
+
end
|
1690
|
+
end
|
1691
|
+
|
1692
|
+
describe 'logger' do
|
1693
|
+
it 'logging works when logging is enabled' do
|
1694
|
+
mock_app do
|
1695
|
+
enable :logging
|
1696
|
+
get '/' do
|
1697
|
+
logger.info "Program started"
|
1698
|
+
logger.warn "Nothing to do!"
|
1699
|
+
end
|
1700
|
+
end
|
1701
|
+
io = StringIO.new
|
1702
|
+
get '/', {}, 'rack.errors' => io
|
1703
|
+
assert io.string.include?("INFO -- : Program started")
|
1704
|
+
assert io.string.include?("WARN -- : Nothing to do")
|
1705
|
+
end
|
1706
|
+
|
1707
|
+
it 'logging works when logging is disable, but no output is produced' do
|
1708
|
+
mock_app do
|
1709
|
+
disable :logging
|
1710
|
+
get '/' do
|
1711
|
+
logger.info "Program started"
|
1712
|
+
logger.warn "Nothing to do!"
|
1713
|
+
end
|
1714
|
+
end
|
1715
|
+
io = StringIO.new
|
1716
|
+
get '/', {}, 'rack.errors' => io
|
1717
|
+
assert !io.string.include?("INFO -- : Program started")
|
1718
|
+
assert !io.string.include?("WARN -- : Nothing to do")
|
1719
|
+
end
|
1720
|
+
|
1721
|
+
it 'does not create a logger when logging is set to nil' do
|
1722
|
+
mock_app do
|
1723
|
+
set :logging, nil
|
1724
|
+
get('/') { logger.inspect }
|
1725
|
+
end
|
1726
|
+
|
1727
|
+
get '/'
|
1728
|
+
assert_body 'nil'
|
1729
|
+
end
|
1730
|
+
end
|
1731
|
+
|
525
1732
|
module ::HelperOne; def one; '1'; end; end
|
526
1733
|
module ::HelperTwo; def two; '2'; end; end
|
527
1734
|
|