sinatra-base 1.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
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