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.
Files changed (101) hide show
  1. data/.yardopts +4 -0
  2. data/AUTHORS +15 -0
  3. data/CHANGES +524 -1
  4. data/Gemfile +82 -0
  5. data/LICENSE +1 -1
  6. data/README.de.rdoc +2093 -0
  7. data/README.es.rdoc +2091 -0
  8. data/README.fr.rdoc +2116 -0
  9. data/README.hu.rdoc +607 -0
  10. data/README.jp.rdoc +514 -23
  11. data/README.pt-br.rdoc +647 -0
  12. data/README.pt-pt.rdoc +646 -0
  13. data/README.rdoc +1580 -205
  14. data/README.ru.rdoc +2015 -0
  15. data/README.zh.rdoc +1816 -0
  16. data/Rakefile +110 -44
  17. data/examples/chat.rb +61 -0
  18. data/examples/simple.rb +3 -0
  19. data/examples/stream.ru +26 -0
  20. data/lib/sinatra.rb +0 -3
  21. data/lib/sinatra/base.rb +923 -393
  22. data/lib/sinatra/main.rb +9 -7
  23. data/lib/sinatra/showexceptions.rb +37 -4
  24. data/lib/sinatra/version.rb +3 -0
  25. data/sinatra-base.gemspec +15 -91
  26. data/test/base_test.rb +2 -2
  27. data/test/builder_test.rb +32 -2
  28. data/test/coffee_test.rb +92 -0
  29. data/test/contest.rb +62 -28
  30. data/test/creole_test.rb +65 -0
  31. data/test/delegator_test.rb +162 -0
  32. data/test/encoding_test.rb +20 -0
  33. data/test/erb_test.rb +25 -2
  34. data/test/extensions_test.rb +1 -1
  35. data/test/filter_test.rb +226 -8
  36. data/test/haml_test.rb +8 -2
  37. data/test/helper.rb +47 -0
  38. data/test/helpers_test.rb +1287 -80
  39. data/test/integration/app.rb +62 -0
  40. data/test/integration_helper.rb +208 -0
  41. data/test/integration_test.rb +82 -0
  42. data/test/less_test.rb +36 -6
  43. data/test/liquid_test.rb +59 -0
  44. data/test/mapped_error_test.rb +84 -7
  45. data/test/markaby_test.rb +80 -0
  46. data/test/markdown_test.rb +81 -0
  47. data/test/middleware_test.rb +1 -1
  48. data/test/nokogiri_test.rb +69 -0
  49. data/test/rack_test.rb +45 -0
  50. data/test/radius_test.rb +59 -0
  51. data/test/rdoc_test.rb +66 -0
  52. data/test/readme_test.rb +136 -0
  53. data/test/request_test.rb +13 -1
  54. data/test/response_test.rb +21 -2
  55. data/test/result_test.rb +5 -5
  56. data/test/route_added_hook_test.rb +1 -1
  57. data/test/routing_test.rb +328 -13
  58. data/test/sass_test.rb +48 -18
  59. data/test/scss_test.rb +88 -0
  60. data/test/server_test.rb +4 -3
  61. data/test/settings_test.rb +191 -21
  62. data/test/sinatra_test.rb +5 -1
  63. data/test/slim_test.rb +88 -0
  64. data/test/static_test.rb +89 -5
  65. data/test/streaming_test.rb +140 -0
  66. data/test/templates_test.rb +143 -4
  67. data/test/textile_test.rb +65 -0
  68. data/test/views/a/in_a.str +1 -0
  69. data/test/views/ascii.erb +2 -0
  70. data/test/views/b/in_b.str +1 -0
  71. data/test/views/calc.html.erb +1 -0
  72. data/test/views/explicitly_nested.str +1 -0
  73. data/test/views/hello.coffee +1 -0
  74. data/test/views/hello.creole +1 -0
  75. data/test/views/hello.liquid +1 -0
  76. data/test/views/hello.mab +1 -0
  77. data/test/views/hello.md +1 -0
  78. data/test/views/hello.nokogiri +1 -0
  79. data/test/views/hello.radius +1 -0
  80. data/test/views/hello.rdoc +1 -0
  81. data/test/views/hello.sass +1 -1
  82. data/test/views/hello.scss +3 -0
  83. data/test/views/hello.slim +1 -0
  84. data/test/views/hello.str +1 -0
  85. data/test/views/hello.textile +1 -0
  86. data/test/views/hello.yajl +1 -0
  87. data/test/views/layout2.liquid +2 -0
  88. data/test/views/layout2.mab +2 -0
  89. data/test/views/layout2.nokogiri +3 -0
  90. data/test/views/layout2.radius +2 -0
  91. data/test/views/layout2.slim +3 -0
  92. data/test/views/layout2.str +2 -0
  93. data/test/views/nested.str +1 -0
  94. data/test/views/utf8.erb +2 -0
  95. data/test/yajl_test.rb +80 -0
  96. metadata +126 -91
  97. data/lib/sinatra/tilt.rb +0 -746
  98. data/test/erubis_test.rb +0 -82
  99. data/test/views/error.erubis +0 -3
  100. data/test/views/hello.erubis +0 -1
  101. data/test/views/layout2.erubis +0 -2
@@ -1,4 +1,4 @@
1
- require File.dirname(__FILE__) + '/helper'
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
- rescue
98
+
99
+ rescue LoadError
94
100
  warn "#{$!.to_s}: skipping haml tests"
95
101
  end
@@ -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.
@@ -1,26 +1,122 @@
1
- require File.dirname(__FILE__) + '/helper'
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
- describe 'status' do
9
- setup do
10
- mock_app {
11
- get '/' do
12
- status 207
13
- nil
14
- end
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
- get '/'
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.empty?
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 => 'utf-8'
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=utf-8', response['Content-Type']
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
- describe 'last_modified' do
426
- setup do
427
- now = Time.now
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 'sets the Last-Modified header to a valid RFC 2616 date value' do
439
- get '/'
440
- assert_equal @now.httpdate, response['Last-Modified']
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 'returns a body when conditional get misses' do
444
- get '/'
445
- assert_equal 200, status
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 'halts when a conditional GET matches' do
450
- get '/', {}, { 'HTTP_IF_MODIFIED_SINCE' => @now.httpdate }
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
- describe 'etag' do
466
- setup do
467
- mock_app {
867
+ it 'does not change a status other than 200' do
868
+ mock_app do
468
869
  get '/' do
469
- body { 'Hello World' }
470
- etag 'FOO'
471
- 'Boo!'
870
+ status 299
871
+ last_modified Time.at(0)
872
+ 'ok'
472
873
  end
473
- }
474
- end
874
+ end
475
875
 
476
- it 'sets the ETag header' do
477
- get '/'
478
- assert_equal '"FOO"', response['ETag']
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
- it 'returns a body when conditional get misses' do
482
- get '/'
483
- assert_equal 200, status
484
- assert_equal 'Boo!', body
485
- end
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
- it 'halts when a conditional GET matches' do
488
- get '/', {}, { 'HTTP_IF_NONE_MATCH' => '"FOO"' }
489
- assert_equal 304, status
490
- assert_equal '', body
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
- it 'should handle multiple ETag values in If-None-Match header' do
494
- get '/', {}, { 'HTTP_IF_NONE_MATCH' => '"BAR", *' }
495
- assert_equal 304, status
496
- assert_equal '', body
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