sinatra-acd 1.4.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (128) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +5 -0
  3. data/AUTHORS +61 -0
  4. data/CHANGES +1293 -0
  5. data/Gemfile +76 -0
  6. data/LICENSE +23 -0
  7. data/README.de.md +2864 -0
  8. data/README.es.md +2786 -0
  9. data/README.fr.md +2924 -0
  10. data/README.hu.md +694 -0
  11. data/README.ja.md +2726 -0
  12. data/README.ko.md +2832 -0
  13. data/README.md +2980 -0
  14. data/README.pt-br.md +965 -0
  15. data/README.pt-pt.md +791 -0
  16. data/README.ru.md +2799 -0
  17. data/README.zh.md +2158 -0
  18. data/Rakefile +199 -0
  19. data/examples/chat.rb +61 -0
  20. data/examples/simple.rb +3 -0
  21. data/examples/stream.ru +26 -0
  22. data/lib/sinatra.rb +4 -0
  23. data/lib/sinatra/base.rb +2044 -0
  24. data/lib/sinatra/images/404.png +0 -0
  25. data/lib/sinatra/images/500.png +0 -0
  26. data/lib/sinatra/main.rb +34 -0
  27. data/lib/sinatra/show_exceptions.rb +345 -0
  28. data/lib/sinatra/version.rb +3 -0
  29. data/sinatra.gemspec +19 -0
  30. data/test/asciidoctor_test.rb +72 -0
  31. data/test/base_test.rb +171 -0
  32. data/test/builder_test.rb +91 -0
  33. data/test/coffee_test.rb +90 -0
  34. data/test/compile_test.rb +183 -0
  35. data/test/contest.rb +100 -0
  36. data/test/creole_test.rb +65 -0
  37. data/test/delegator_test.rb +160 -0
  38. data/test/encoding_test.rb +20 -0
  39. data/test/erb_test.rb +116 -0
  40. data/test/extensions_test.rb +98 -0
  41. data/test/filter_test.rb +487 -0
  42. data/test/haml_test.rb +109 -0
  43. data/test/helper.rb +131 -0
  44. data/test/helpers_test.rb +1917 -0
  45. data/test/integration/app.rb +79 -0
  46. data/test/integration_helper.rb +236 -0
  47. data/test/integration_test.rb +104 -0
  48. data/test/less_test.rb +69 -0
  49. data/test/liquid_test.rb +77 -0
  50. data/test/mapped_error_test.rb +285 -0
  51. data/test/markaby_test.rb +80 -0
  52. data/test/markdown_test.rb +82 -0
  53. data/test/mediawiki_test.rb +68 -0
  54. data/test/middleware_test.rb +68 -0
  55. data/test/nokogiri_test.rb +67 -0
  56. data/test/public/favicon.ico +0 -0
  57. data/test/rabl_test.rb +89 -0
  58. data/test/rack_test.rb +45 -0
  59. data/test/radius_test.rb +59 -0
  60. data/test/rdoc_test.rb +66 -0
  61. data/test/readme_test.rb +130 -0
  62. data/test/request_test.rb +97 -0
  63. data/test/response_test.rb +63 -0
  64. data/test/result_test.rb +76 -0
  65. data/test/route_added_hook_test.rb +59 -0
  66. data/test/routing_test.rb +1412 -0
  67. data/test/sass_test.rb +115 -0
  68. data/test/scss_test.rb +88 -0
  69. data/test/server_test.rb +48 -0
  70. data/test/settings_test.rb +582 -0
  71. data/test/sinatra_test.rb +12 -0
  72. data/test/slim_test.rb +102 -0
  73. data/test/static_test.rb +236 -0
  74. data/test/streaming_test.rb +149 -0
  75. data/test/stylus_test.rb +90 -0
  76. data/test/templates_test.rb +382 -0
  77. data/test/textile_test.rb +65 -0
  78. data/test/views/a/in_a.str +1 -0
  79. data/test/views/ascii.erb +2 -0
  80. data/test/views/b/in_b.str +1 -0
  81. data/test/views/calc.html.erb +1 -0
  82. data/test/views/error.builder +3 -0
  83. data/test/views/error.erb +3 -0
  84. data/test/views/error.haml +3 -0
  85. data/test/views/error.sass +2 -0
  86. data/test/views/explicitly_nested.str +1 -0
  87. data/test/views/foo/hello.test +1 -0
  88. data/test/views/hello.asciidoc +1 -0
  89. data/test/views/hello.builder +1 -0
  90. data/test/views/hello.coffee +1 -0
  91. data/test/views/hello.creole +1 -0
  92. data/test/views/hello.erb +1 -0
  93. data/test/views/hello.haml +1 -0
  94. data/test/views/hello.less +5 -0
  95. data/test/views/hello.liquid +1 -0
  96. data/test/views/hello.mab +1 -0
  97. data/test/views/hello.md +1 -0
  98. data/test/views/hello.mediawiki +1 -0
  99. data/test/views/hello.nokogiri +1 -0
  100. data/test/views/hello.rabl +2 -0
  101. data/test/views/hello.radius +1 -0
  102. data/test/views/hello.rdoc +1 -0
  103. data/test/views/hello.sass +2 -0
  104. data/test/views/hello.scss +3 -0
  105. data/test/views/hello.slim +1 -0
  106. data/test/views/hello.str +1 -0
  107. data/test/views/hello.styl +2 -0
  108. data/test/views/hello.test +1 -0
  109. data/test/views/hello.textile +1 -0
  110. data/test/views/hello.wlang +1 -0
  111. data/test/views/hello.yajl +1 -0
  112. data/test/views/layout2.builder +3 -0
  113. data/test/views/layout2.erb +2 -0
  114. data/test/views/layout2.haml +2 -0
  115. data/test/views/layout2.liquid +2 -0
  116. data/test/views/layout2.mab +2 -0
  117. data/test/views/layout2.nokogiri +3 -0
  118. data/test/views/layout2.rabl +3 -0
  119. data/test/views/layout2.radius +2 -0
  120. data/test/views/layout2.slim +3 -0
  121. data/test/views/layout2.str +2 -0
  122. data/test/views/layout2.test +1 -0
  123. data/test/views/layout2.wlang +2 -0
  124. data/test/views/nested.str +1 -0
  125. data/test/views/utf8.erb +2 -0
  126. data/test/wlang_test.rb +87 -0
  127. data/test/yajl_test.rb +86 -0
  128. metadata +280 -0
@@ -0,0 +1,130 @@
1
+ # Tests to check if all the README examples work.
2
+ require File.expand_path('../helper', __FILE__)
3
+
4
+ class ReadmeTest < Test::Unit::TestCase
5
+ example do
6
+ mock_app { get('/') { 'Hello world!' } }
7
+ get '/'
8
+ assert_body 'Hello world!'
9
+ end
10
+
11
+ section "Routes" do
12
+ example do
13
+ mock_app do
14
+ get('/') { ".. show something .." }
15
+
16
+ post('/') { ".. create something .." }
17
+
18
+ put('/') { ".. replace something .." }
19
+
20
+ patch('/') { ".. modify something .." }
21
+
22
+ delete('/') { ".. annihilate something .." }
23
+
24
+ options('/') { ".. appease something .." }
25
+
26
+ link('/') { ".. affiliate something .." }
27
+
28
+ unlink('/') { ".. separate something .." }
29
+ end
30
+
31
+ get '/'
32
+ assert_body '.. show something ..'
33
+
34
+ post '/'
35
+ assert_body '.. create something ..'
36
+
37
+ put '/'
38
+ assert_body '.. replace something ..'
39
+
40
+ patch '/'
41
+ assert_body '.. modify something ..'
42
+
43
+ delete '/'
44
+ assert_body '.. annihilate something ..'
45
+
46
+ options '/'
47
+ assert_body '.. appease something ..'
48
+
49
+ link '/'
50
+ assert_body '.. affiliate something ..'
51
+
52
+ unlink '/'
53
+ assert_body '.. separate something ..'
54
+ end
55
+
56
+ example do
57
+ mock_app do
58
+ get('/hello/:name') do
59
+ # matches "GET /hello/foo" and "GET /hello/bar"
60
+ # params[:name] is 'foo' or 'bar'
61
+ "Hello #{params[:name]}!"
62
+ end
63
+ end
64
+
65
+ get '/hello/foo'
66
+ assert_body 'Hello foo!'
67
+
68
+ get '/hello/bar'
69
+ assert_body 'Hello bar!'
70
+ end
71
+
72
+ example do
73
+ mock_app { get('/hello/:name') { |n| "Hello #{n}!" } }
74
+
75
+ get '/hello/foo'
76
+ assert_body 'Hello foo!'
77
+
78
+ get '/hello/bar'
79
+ assert_body 'Hello bar!'
80
+ end
81
+
82
+ example do
83
+ mock_app do
84
+ get('/say/*/to/*') do
85
+ # matches /say/hello/to/world
86
+ params[:splat].inspect # => ["hello", "world"]
87
+ end
88
+
89
+ get('/download/*.*') do
90
+ # matches /download/path/to/file.xml
91
+ params[:splat].inspect # => ["path/to/file", "xml"]
92
+ end
93
+ end
94
+
95
+ get "/say/hello/to/world"
96
+ assert_body '["hello", "world"]'
97
+
98
+ get "/download/path/to/file.xml"
99
+ assert_body '["path/to/file", "xml"]'
100
+ end
101
+
102
+ example do
103
+ mock_app do
104
+ get(%r{/hello/([\w]+)}) {
105
+ "Hello, #{params[:captures].first}!"
106
+ }
107
+ end
108
+
109
+ get '/hello/foo'
110
+ assert_body 'Hello, foo!'
111
+
112
+ get '/hello/bar'
113
+ assert_body 'Hello, bar!'
114
+ end
115
+
116
+ example do
117
+ mock_app do
118
+ get( %r{/hello/([\w]+)}) { |c|
119
+ "Hello, #{c}!"
120
+ }
121
+ end
122
+
123
+ get '/hello/foo'
124
+ assert_body 'Hello, foo!'
125
+
126
+ get '/hello/bar'
127
+ assert_body 'Hello, bar!'
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,97 @@
1
+ require File.expand_path('../helper', __FILE__)
2
+ require 'stringio'
3
+
4
+ class RequestTest < Test::Unit::TestCase
5
+ it 'responds to #user_agent' do
6
+ request = Sinatra::Request.new({'HTTP_USER_AGENT' => 'Test'})
7
+ assert request.respond_to?(:user_agent)
8
+ assert_equal 'Test', request.user_agent
9
+ end
10
+
11
+ it 'parses POST params when Content-Type is form-dataish' do
12
+ request = Sinatra::Request.new(
13
+ 'REQUEST_METHOD' => 'PUT',
14
+ 'CONTENT_TYPE' => 'application/x-www-form-urlencoded',
15
+ 'rack.input' => StringIO.new('foo=bar')
16
+ )
17
+ assert_equal 'bar', request.params['foo']
18
+ end
19
+
20
+ it 'is secure when the url scheme is https' do
21
+ request = Sinatra::Request.new('rack.url_scheme' => 'https')
22
+ assert request.secure?
23
+ end
24
+
25
+ it 'is not secure when the url scheme is http' do
26
+ request = Sinatra::Request.new('rack.url_scheme' => 'http')
27
+ assert !request.secure?
28
+ end
29
+
30
+ it 'respects X-Forwarded-Proto header for proxied SSL' do
31
+ request = Sinatra::Request.new('HTTP_X_FORWARDED_PROTO' => 'https')
32
+ assert request.secure?
33
+ end
34
+
35
+ it 'is possible to marshal params' do
36
+ request = Sinatra::Request.new(
37
+ 'REQUEST_METHOD' => 'PUT',
38
+ 'CONTENT_TYPE' => 'application/x-www-form-urlencoded',
39
+ 'rack.input' => StringIO.new('foo=bar')
40
+ )
41
+ Sinatra::Base.new!.send(:indifferent_hash).replace(request.params)
42
+ dumped = Marshal.dump(request.params)
43
+ assert_equal 'bar', Marshal.load(dumped)['foo']
44
+ end
45
+
46
+ it "exposes the preferred type's parameters" do
47
+ request = Sinatra::Request.new(
48
+ 'HTTP_ACCEPT' => 'image/jpeg; compress=0.25'
49
+ )
50
+ assert_equal({ 'compress' => '0.25' }, request.preferred_type.params)
51
+ end
52
+
53
+ it "makes accept types behave like strings" do
54
+ request = Sinatra::Request.new('HTTP_ACCEPT' => 'image/jpeg; compress=0.25')
55
+ assert request.accept?('image/jpeg')
56
+ assert_equal 'image/jpeg', request.preferred_type.to_s
57
+ assert_equal 'image/jpeg', request.preferred_type.to_str
58
+ assert_equal 'image', request.preferred_type.split('/').first
59
+
60
+ String.instance_methods.each do |method|
61
+ next unless "".respond_to? method
62
+ assert request.preferred_type.respond_to?(method), "responds to #{method}"
63
+ end
64
+ end
65
+
66
+ it "accepts types when wildcards are requested" do
67
+ request = Sinatra::Request.new('HTTP_ACCEPT' => 'image/*')
68
+ assert request.accept?('image/jpeg')
69
+ end
70
+
71
+ it "properly decodes MIME type parameters" do
72
+ request = Sinatra::Request.new(
73
+ 'HTTP_ACCEPT' => 'image/jpeg;unquoted=0.25;quoted="0.25";chartest="\";,\x"'
74
+ )
75
+ expected = { 'unquoted' => '0.25', 'quoted' => '0.25', 'chartest' => '";,x' }
76
+ assert_equal(expected, request.preferred_type.params)
77
+ end
78
+
79
+ it 'accepts */* when HTTP_ACCEPT is not present in the request' do
80
+ request = Sinatra::Request.new Hash.new
81
+ assert_equal 1, request.accept.size
82
+ assert request.accept?('text/html')
83
+ assert_equal '*/*', request.preferred_type.to_s
84
+ end
85
+
86
+ it 'accepts */* when HTTP_ACCEPT is blank in the request' do
87
+ request = Sinatra::Request.new 'HTTP_ACCEPT' => ''
88
+ assert_equal 1, request.accept.size
89
+ assert request.accept?('text/html')
90
+ assert_equal '*/*', request.preferred_type.to_s
91
+ end
92
+
93
+ it 'will not accept types not specified in HTTP_ACCEPT when HTTP_ACCEPT is provided' do
94
+ request = Sinatra::Request.new 'HTTP_ACCEPT' => 'application/json'
95
+ assert !request.accept?('text/html')
96
+ end
97
+ end
@@ -0,0 +1,63 @@
1
+ # encoding: utf-8
2
+
3
+ require File.expand_path('../helper', __FILE__)
4
+
5
+ class ResponseTest < Test::Unit::TestCase
6
+ setup { @response = Sinatra::Response.new }
7
+
8
+ def assert_same_body(a, b)
9
+ assert_equal a.to_enum(:each).to_a, b.to_enum(:each).to_a
10
+ end
11
+
12
+ it "initializes with 200, text/html, and empty body" do
13
+ assert_equal 200, @response.status
14
+ assert_equal 'text/html', @response['Content-Type']
15
+ assert_equal [], @response.body
16
+ end
17
+
18
+ it 'uses case insensitive headers' do
19
+ @response['content-type'] = 'application/foo'
20
+ assert_equal 'application/foo', @response['Content-Type']
21
+ assert_equal 'application/foo', @response['CONTENT-TYPE']
22
+ end
23
+
24
+ it 'writes to body' do
25
+ @response.body = 'Hello'
26
+ @response.write ' World'
27
+ assert_equal 'Hello World', @response.body.join
28
+ end
29
+
30
+ [204, 304].each do |status_code|
31
+ it "removes the Content-Type header and body when response status is #{status_code}" do
32
+ @response.status = status_code
33
+ @response.body = ['Hello World']
34
+ assert_equal [status_code, {}, []], @response.finish
35
+ end
36
+ end
37
+
38
+ it 'Calculates the Content-Length using the bytesize of the body' do
39
+ @response.body = ['Hello', 'World!', '✈']
40
+ _, headers, body = @response.finish
41
+ assert_equal '14', headers['Content-Length']
42
+ assert_same_body @response.body, body
43
+ end
44
+
45
+ it 'does not call #to_ary or #inject on the body' do
46
+ object = Object.new
47
+ def object.inject(*) fail 'called' end
48
+ def object.to_ary(*) fail 'called' end
49
+ def object.each(*) end
50
+ @response.body = object
51
+ assert @response.finish
52
+ end
53
+
54
+ it 'does not nest a Sinatra::Response' do
55
+ @response.body = Sinatra::Response.new ["foo"]
56
+ assert_same_body @response.body, ["foo"]
57
+ end
58
+
59
+ it 'does not nest a Rack::Response' do
60
+ @response.body = Rack::Response.new ["foo"]
61
+ assert_same_body @response.body, ["foo"]
62
+ end
63
+ end
@@ -0,0 +1,76 @@
1
+ require File.expand_path('../helper', __FILE__)
2
+
3
+ class ResultTest < Test::Unit::TestCase
4
+ it "sets response.body when result is a String" do
5
+ mock_app { get('/') { 'Hello World' } }
6
+
7
+ get '/'
8
+ assert ok?
9
+ assert_equal 'Hello World', body
10
+ end
11
+
12
+ it "sets response.body when result is an Array of Strings" do
13
+ mock_app { get('/') { ['Hello', 'World'] } }
14
+
15
+ get '/'
16
+ assert ok?
17
+ assert_equal 'HelloWorld', body
18
+ end
19
+
20
+ it "sets response.body when result responds to #each" do
21
+ mock_app do
22
+ get('/') do
23
+ res = lambda { 'Hello World' }
24
+ def res.each ; yield call ; end
25
+ return res
26
+ end
27
+ end
28
+
29
+ get '/'
30
+ assert ok?
31
+ assert_equal 'Hello World', body
32
+ end
33
+
34
+ it "sets response.body to [] when result is nil" do
35
+ mock_app { get( '/') { nil } }
36
+
37
+ get '/'
38
+ assert ok?
39
+ assert_equal '', body
40
+ end
41
+
42
+ it "sets status, headers, and body when result is a Rack response tuple" do
43
+ mock_app {
44
+ get('/') { [203, {'Content-Type' => 'foo/bar'}, 'Hello World'] }
45
+ }
46
+
47
+ get '/'
48
+ assert_equal 203, status
49
+ assert_equal 'foo/bar', response['Content-Type']
50
+ assert_equal 'Hello World', body
51
+ end
52
+
53
+ it "sets status and body when result is a two-tuple" do
54
+ mock_app { get('/') { [409, 'formula of'] } }
55
+
56
+ get '/'
57
+ assert_equal 409, status
58
+ assert_equal 'formula of', body
59
+ end
60
+
61
+ it "raises a ArgumentError when result is a non two or three tuple Array" do
62
+ mock_app {
63
+ get('/') { [409, 'formula of', 'something else', 'even more'] }
64
+ }
65
+
66
+ assert_raise(ArgumentError) { get '/' }
67
+ end
68
+
69
+ it "sets status when result is a Fixnum status code" do
70
+ mock_app { get('/') { 205 } }
71
+
72
+ get '/'
73
+ assert_equal 205, status
74
+ assert_equal '', body
75
+ end
76
+ end
@@ -0,0 +1,59 @@
1
+ require File.expand_path('../helper', __FILE__)
2
+
3
+ module RouteAddedTest
4
+ @routes, @procs = [], []
5
+ def self.routes ; @routes ; end
6
+ def self.procs ; @procs ; end
7
+ def self.route_added(verb, path, proc)
8
+ @routes << [verb, path]
9
+ @procs << proc
10
+ end
11
+ end
12
+
13
+ class RouteAddedHookTest < Test::Unit::TestCase
14
+ setup do
15
+ RouteAddedTest.routes.clear
16
+ RouteAddedTest.procs.clear
17
+ end
18
+
19
+ it "should be notified of an added route" do
20
+ mock_app(Class.new(Sinatra::Base)) do
21
+ register RouteAddedTest
22
+ get('/') {}
23
+ end
24
+
25
+ assert_equal [["GET", "/"], ["HEAD", "/"]],
26
+ RouteAddedTest.routes
27
+ end
28
+
29
+ it "should include hooks from superclass" do
30
+ a = Class.new(Class.new(Sinatra::Base))
31
+ b = Class.new(a)
32
+
33
+ a.register RouteAddedTest
34
+ b.class_eval { post("/sub_app_route") {} }
35
+
36
+ assert_equal [["POST", "/sub_app_route"]],
37
+ RouteAddedTest.routes
38
+ end
39
+
40
+ it "should only run once per extension" do
41
+ mock_app(Class.new(Sinatra::Base)) do
42
+ register RouteAddedTest
43
+ register RouteAddedTest
44
+ get('/') {}
45
+ end
46
+
47
+ assert_equal [["GET", "/"], ["HEAD", "/"]],
48
+ RouteAddedTest.routes
49
+ end
50
+
51
+ it "should pass route blocks as an argument" do
52
+ mock_app(Class.new(Sinatra::Base)) do
53
+ register RouteAddedTest
54
+ get('/') {}
55
+ end
56
+
57
+ assert_kind_of Proc, RouteAddedTest.procs.first
58
+ end
59
+ end
@@ -0,0 +1,1412 @@
1
+ # I like coding: UTF-8
2
+ require File.expand_path('../helper', __FILE__)
3
+
4
+ # Helper method for easy route pattern matching testing
5
+ def route_def(pattern)
6
+ mock_app { get(pattern) { } }
7
+ end
8
+
9
+ class RegexpLookAlike
10
+ class MatchData
11
+ def captures
12
+ ["this", "is", "a", "test"]
13
+ end
14
+ end
15
+
16
+ def match(string)
17
+ ::RegexpLookAlike::MatchData.new if string == "/this/is/a/test/"
18
+ end
19
+
20
+ def keys
21
+ ["one", "two", "three", "four"]
22
+ end
23
+ end
24
+
25
+ class RoutingTest < Test::Unit::TestCase
26
+ %w[get put post delete options patch link unlink].each do |verb|
27
+ it "defines #{verb.upcase} request handlers with #{verb}" do
28
+ mock_app {
29
+ send verb, '/hello' do
30
+ 'Hello World'
31
+ end
32
+ }
33
+
34
+ request = Rack::MockRequest.new(@app)
35
+ response = request.request(verb.upcase, '/hello', {})
36
+ assert response.ok?
37
+ assert_equal 'Hello World', response.body
38
+ end
39
+ end
40
+
41
+ it "defines HEAD request handlers with HEAD" do
42
+ mock_app {
43
+ head '/hello' do
44
+ response['X-Hello'] = 'World!'
45
+ 'remove me'
46
+ end
47
+ }
48
+
49
+ request = Rack::MockRequest.new(@app)
50
+ response = request.request('HEAD', '/hello', {})
51
+ assert response.ok?
52
+ assert_equal 'World!', response['X-Hello']
53
+ assert_equal '', response.body
54
+ end
55
+
56
+ it "404s when no route satisfies the request" do
57
+ mock_app {
58
+ get('/foo') { }
59
+ }
60
+ get '/bar'
61
+ assert_equal 404, status
62
+ end
63
+
64
+ it "404s and sets X-Cascade header when no route satisfies the request" do
65
+ mock_app {
66
+ get('/foo') { }
67
+ }
68
+ get '/bar'
69
+ assert_equal 404, status
70
+ assert_equal 'pass', response.headers['X-Cascade']
71
+ end
72
+
73
+ it "404s and does not set X-Cascade header when no route satisfies the request and x_cascade has been disabled" do
74
+ mock_app {
75
+ disable :x_cascade
76
+ get('/foo') { }
77
+ }
78
+ get '/bar'
79
+ assert_equal 404, status
80
+ assert_equal nil, response.headers['X-Cascade']
81
+ end
82
+
83
+
84
+ it "allows using unicode" do
85
+ mock_app do
86
+ get('/föö') { }
87
+ end
88
+ get '/f%C3%B6%C3%B6'
89
+ assert_equal 200, status
90
+ end
91
+
92
+ it "it handles encoded slashes correctly" do
93
+ mock_app {
94
+ set :protection, :except => :path_traversal
95
+ get("/:a") { |a| a }
96
+ }
97
+ get '/foo%2Fbar'
98
+ assert_equal 200, status
99
+ assert_body "foo/bar"
100
+ end
101
+
102
+ it "overrides the content-type in error handlers" do
103
+ mock_app {
104
+ before { content_type 'text/plain' }
105
+ error Sinatra::NotFound do
106
+ content_type "text/html"
107
+ "<h1>Not Found</h1>"
108
+ end
109
+ }
110
+
111
+ get '/foo'
112
+ assert_equal 404, status
113
+ assert_equal 'text/html;charset=utf-8', response["Content-Type"]
114
+ assert_equal "<h1>Not Found</h1>", response.body
115
+ end
116
+
117
+ it "recalculates body length correctly for 404 response" do
118
+ mock_app {
119
+ get '/' do
120
+ @response["Content-Length"] = "30"
121
+ raise Sinatra::NotFound
122
+ end
123
+ }
124
+
125
+ get "/"
126
+ assert_equal "18", response["Content-Length"]
127
+ assert_equal 404, status
128
+ end
129
+
130
+ it 'matches empty PATH_INFO to "/" if no route is defined for ""' do
131
+ mock_app do
132
+ get '/' do
133
+ 'worked'
134
+ end
135
+ end
136
+
137
+ get '/', {}, "PATH_INFO" => ""
138
+ assert ok?
139
+ assert_equal 'worked', body
140
+ end
141
+
142
+ it 'matches empty PATH_INFO to "" if a route is defined for ""' do
143
+ mock_app do
144
+ disable :protection
145
+
146
+ get '/' do
147
+ 'did not work'
148
+ end
149
+
150
+ get '' do
151
+ 'worked'
152
+ end
153
+ end
154
+
155
+ get '/', {}, "PATH_INFO" => ""
156
+ assert ok?
157
+ assert_equal 'worked', body
158
+ end
159
+
160
+ it 'takes multiple definitions of a route' do
161
+ mock_app {
162
+ user_agent(/Foo/)
163
+ get '/foo' do
164
+ 'foo'
165
+ end
166
+
167
+ get '/foo' do
168
+ 'not foo'
169
+ end
170
+ }
171
+
172
+ get '/foo', {}, 'HTTP_USER_AGENT' => 'Foo'
173
+ assert ok?
174
+ assert_equal 'foo', body
175
+
176
+ get '/foo'
177
+ assert ok?
178
+ assert_equal 'not foo', body
179
+ end
180
+
181
+ it "exposes params with indifferent hash" do
182
+ mock_app {
183
+ get '/:foo' do
184
+ assert_equal 'bar', params['foo']
185
+ assert_equal 'bar', params[:foo]
186
+ 'well, alright'
187
+ end
188
+ }
189
+ get '/bar'
190
+ assert_equal 'well, alright', body
191
+ end
192
+
193
+ it "merges named params and query string params in params" do
194
+ mock_app {
195
+ get '/:foo' do
196
+ assert_equal 'bar', params['foo']
197
+ assert_equal 'biz', params['baz']
198
+ end
199
+ }
200
+ get '/bar?baz=biz'
201
+ assert ok?
202
+ end
203
+
204
+ it "supports named params like /hello/:person" do
205
+ mock_app {
206
+ get '/hello/:person' do
207
+ "Hello #{params['person']}"
208
+ end
209
+ }
210
+ get '/hello/Frank'
211
+ assert_equal 'Hello Frank', body
212
+ end
213
+
214
+ it "supports optional named params like /?:foo?/?:bar?" do
215
+ mock_app {
216
+ get '/?:foo?/?:bar?' do
217
+ "foo=#{params[:foo]};bar=#{params[:bar]}"
218
+ end
219
+ }
220
+
221
+ get '/hello/world'
222
+ assert ok?
223
+ assert_equal "foo=hello;bar=world", body
224
+
225
+ get '/hello'
226
+ assert ok?
227
+ assert_equal "foo=hello;bar=", body
228
+
229
+ get '/'
230
+ assert ok?
231
+ assert_equal "foo=;bar=", body
232
+ end
233
+
234
+ it "supports named captures like %r{/hello/(?<person>[^/?#]+)} on Ruby >= 1.9" do
235
+ next if RUBY_VERSION < '1.9'
236
+ mock_app {
237
+ get Regexp.new('/hello/(?<person>[^/?#]+)') do
238
+ "Hello #{params['person']}"
239
+ end
240
+ }
241
+ get '/hello/Frank'
242
+ assert_equal 'Hello Frank', body
243
+ end
244
+
245
+ it "supports optional named captures like %r{/page(?<format>.[^/?#]+)?} on Ruby >= 1.9" do
246
+ next if RUBY_VERSION < '1.9'
247
+ mock_app {
248
+ get Regexp.new('/page(?<format>.[^/?#]+)?') do
249
+ "format=#{params[:format]}"
250
+ end
251
+ }
252
+
253
+ get '/page.html'
254
+ assert ok?
255
+ assert_equal "format=.html", body
256
+
257
+ get '/page.xml'
258
+ assert ok?
259
+ assert_equal "format=.xml", body
260
+
261
+ get '/page'
262
+ assert ok?
263
+ assert_equal "format=", body
264
+ end
265
+
266
+ it 'does not concatinate params with the same name' do
267
+ mock_app { get('/:foo') { params[:foo] } }
268
+ get '/a?foo=b'
269
+ assert_body 'a'
270
+ end
271
+
272
+ it "supports single splat params like /*" do
273
+ mock_app {
274
+ get '/*' do
275
+ assert params['splat'].kind_of?(Array)
276
+ params['splat'].join "\n"
277
+ end
278
+ }
279
+
280
+ get '/foo'
281
+ assert_equal "foo", body
282
+
283
+ get '/foo/bar/baz'
284
+ assert_equal "foo/bar/baz", body
285
+ end
286
+
287
+ it "supports mixing multiple splat params like /*/foo/*/*" do
288
+ mock_app {
289
+ get '/*/foo/*/*' do
290
+ assert params['splat'].kind_of?(Array)
291
+ params['splat'].join "\n"
292
+ end
293
+ }
294
+
295
+ get '/bar/foo/bling/baz/boom'
296
+ assert_equal "bar\nbling\nbaz/boom", body
297
+
298
+ get '/bar/foo/baz'
299
+ assert not_found?
300
+ end
301
+
302
+ it "supports mixing named and splat params like /:foo/*" do
303
+ mock_app {
304
+ get '/:foo/*' do
305
+ assert_equal 'foo', params['foo']
306
+ assert_equal ['bar/baz'], params['splat']
307
+ end
308
+ }
309
+
310
+ get '/foo/bar/baz'
311
+ assert ok?
312
+ end
313
+
314
+ it "matches a dot ('.') as part of a named param" do
315
+ mock_app {
316
+ get '/:foo/:bar' do
317
+ params[:foo]
318
+ end
319
+ }
320
+
321
+ get '/user@example.com/name'
322
+ assert_equal 200, response.status
323
+ assert_equal 'user@example.com', body
324
+ end
325
+
326
+ it "matches a literal dot ('.') outside of named params" do
327
+ mock_app {
328
+ get '/:file.:ext' do
329
+ assert_equal 'pony', params[:file]
330
+ assert_equal 'jpg', params[:ext]
331
+ 'right on'
332
+ end
333
+ }
334
+
335
+ get '/pony.jpg'
336
+ assert_equal 200, response.status
337
+ assert_equal 'right on', body
338
+ end
339
+
340
+ it "literally matches dot in paths" do
341
+ route_def '/test.bar'
342
+
343
+ get '/test.bar'
344
+ assert ok?
345
+ get 'test0bar'
346
+ assert not_found?
347
+ end
348
+
349
+ it "literally matches dollar sign in paths" do
350
+ route_def '/test$/'
351
+
352
+ get '/test$/'
353
+ assert ok?
354
+ end
355
+
356
+ it "literally matches plus sign in paths" do
357
+ route_def '/te+st/'
358
+
359
+ get '/te%2Bst/'
360
+ assert ok?
361
+ get '/teeeeeeest/'
362
+ assert not_found?
363
+ end
364
+
365
+ it "does not convert plus sign into space as the value of a named param" do
366
+ mock_app do
367
+ get '/:test' do
368
+ params["test"]
369
+ end
370
+ end
371
+ get '/bob+ross'
372
+ assert ok?
373
+ assert_equal 'bob+ross', body
374
+ end
375
+
376
+ it "literally matches parens in paths" do
377
+ route_def '/test(bar)/'
378
+
379
+ get '/test(bar)/'
380
+ assert ok?
381
+ end
382
+
383
+ it "supports basic nested params" do
384
+ mock_app {
385
+ get '/hi' do
386
+ params["person"]["name"]
387
+ end
388
+ }
389
+
390
+ get "/hi?person[name]=John+Doe"
391
+ assert ok?
392
+ assert_equal "John Doe", body
393
+ end
394
+
395
+ it "exposes nested params with indifferent hash" do
396
+ mock_app {
397
+ get '/testme' do
398
+ assert_equal 'baz', params['bar']['foo']
399
+ assert_equal 'baz', params['bar'][:foo]
400
+ 'well, alright'
401
+ end
402
+ }
403
+ get '/testme?bar[foo]=baz'
404
+ assert_equal 'well, alright', body
405
+ end
406
+
407
+ it "exposes params nested within arrays with indifferent hash" do
408
+ mock_app {
409
+ get '/testme' do
410
+ assert_equal 'baz', params['bar'][0]['foo']
411
+ assert_equal 'baz', params['bar'][0][:foo]
412
+ 'well, alright'
413
+ end
414
+ }
415
+ get '/testme?bar[][foo]=baz'
416
+ assert_equal 'well, alright', body
417
+ end
418
+
419
+ it "supports arrays within params" do
420
+ mock_app {
421
+ get '/foo' do
422
+ assert_equal ['A', 'B'], params['bar']
423
+ 'looks good'
424
+ end
425
+ }
426
+ get '/foo?bar[]=A&bar[]=B'
427
+ assert ok?
428
+ assert_equal 'looks good', body
429
+ end
430
+
431
+ it "supports deeply nested params" do
432
+ expected_params = {
433
+ "emacs" => {
434
+ "map" => { "goto-line" => "M-g g" },
435
+ "version" => "22.3.1"
436
+ },
437
+ "browser" => {
438
+ "firefox" => {"engine" => {"name"=>"spidermonkey", "version"=>"1.7.0"}},
439
+ "chrome" => {"engine" => {"name"=>"V8", "version"=>"1.0"}}
440
+ },
441
+ "paste" => {"name"=>"hello world", "syntax"=>"ruby"}
442
+ }
443
+ mock_app {
444
+ get '/foo' do
445
+ assert_equal expected_params, params
446
+ 'looks good'
447
+ end
448
+ }
449
+ get '/foo', expected_params
450
+ assert ok?
451
+ assert_equal 'looks good', body
452
+ end
453
+
454
+ it "preserves non-nested params" do
455
+ mock_app {
456
+ get '/foo' do
457
+ assert_equal "2", params["article_id"]
458
+ assert_equal "awesome", params['comment']['body']
459
+ assert_nil params['comment[body]']
460
+ 'looks good'
461
+ end
462
+ }
463
+
464
+ get '/foo?article_id=2&comment[body]=awesome'
465
+ assert ok?
466
+ assert_equal 'looks good', body
467
+ end
468
+
469
+ it "matches paths that include spaces encoded with %20" do
470
+ mock_app {
471
+ get '/path with spaces' do
472
+ 'looks good'
473
+ end
474
+ }
475
+
476
+ get '/path%20with%20spaces'
477
+ assert ok?
478
+ assert_equal 'looks good', body
479
+ end
480
+
481
+ it "matches paths that include spaces encoded with +" do
482
+ mock_app {
483
+ get '/path with spaces' do
484
+ 'looks good'
485
+ end
486
+ }
487
+
488
+ get '/path+with+spaces'
489
+ assert ok?
490
+ assert_equal 'looks good', body
491
+ end
492
+
493
+ it "matches paths that include ampersands" do
494
+ mock_app {
495
+ get '/:name' do
496
+ 'looks good'
497
+ end
498
+ }
499
+
500
+ get '/foo&bar'
501
+ assert ok?
502
+ assert_equal 'looks good', body
503
+ end
504
+
505
+ it "URL decodes named parameters and splats" do
506
+ mock_app {
507
+ get '/:foo/*' do
508
+ assert_equal 'hello world', params['foo']
509
+ assert_equal ['how are you'], params['splat']
510
+ nil
511
+ end
512
+ }
513
+
514
+ get '/hello%20world/how%20are%20you'
515
+ assert ok?
516
+ end
517
+
518
+ it 'supports regular expressions' do
519
+ mock_app {
520
+ get(/^\/foo...\/bar$/) do
521
+ 'Hello World'
522
+ end
523
+ }
524
+
525
+ get '/foooom/bar'
526
+ assert ok?
527
+ assert_equal 'Hello World', body
528
+ end
529
+
530
+ it 'makes regular expression captures available in params[:captures]' do
531
+ mock_app {
532
+ get(/^\/fo(.*)\/ba(.*)/) do
533
+ assert_equal ['orooomma', 'f'], params[:captures]
534
+ 'right on'
535
+ end
536
+ }
537
+
538
+ get '/foorooomma/baf'
539
+ assert ok?
540
+ assert_equal 'right on', body
541
+ end
542
+
543
+ it 'supports regular expression look-alike routes' do
544
+ mock_app {
545
+ get(RegexpLookAlike.new) do
546
+ assert_equal 'this', params[:one]
547
+ assert_equal 'is', params[:two]
548
+ assert_equal 'a', params[:three]
549
+ assert_equal 'test', params[:four]
550
+ 'right on'
551
+ end
552
+ }
553
+
554
+ get '/this/is/a/test/'
555
+ assert ok?
556
+ assert_equal 'right on', body
557
+ end
558
+
559
+ it 'raises a TypeError when pattern is not a String or Regexp' do
560
+ assert_raise(TypeError) {
561
+ mock_app { get(42){} }
562
+ }
563
+ end
564
+
565
+ it "returns response immediately on halt" do
566
+ mock_app {
567
+ get '/' do
568
+ halt 'Hello World'
569
+ 'Boo-hoo World'
570
+ end
571
+ }
572
+
573
+ get '/'
574
+ assert ok?
575
+ assert_equal 'Hello World', body
576
+ end
577
+
578
+ it "halts with a response tuple" do
579
+ mock_app {
580
+ get '/' do
581
+ halt 295, {'Content-Type' => 'text/plain'}, 'Hello World'
582
+ end
583
+ }
584
+
585
+ get '/'
586
+ assert_equal 295, status
587
+ assert_equal 'text/plain', response['Content-Type']
588
+ assert_equal 'Hello World', body
589
+ end
590
+
591
+ it "halts with an array of strings" do
592
+ mock_app {
593
+ get '/' do
594
+ halt %w[Hello World How Are You]
595
+ end
596
+ }
597
+
598
+ get '/'
599
+ assert_equal 'HelloWorldHowAreYou', body
600
+ end
601
+
602
+ it 'sets response.status with halt' do
603
+ status_was = nil
604
+ mock_app do
605
+ after { status_was = status }
606
+ get('/') { halt 500, 'error' }
607
+ end
608
+ get '/'
609
+ assert_status 500
610
+ assert_equal 500, status_was
611
+ end
612
+
613
+ it "transitions to the next matching route on pass" do
614
+ mock_app {
615
+ get '/:foo' do
616
+ pass
617
+ 'Hello Foo'
618
+ end
619
+
620
+ get '/*' do
621
+ assert !params.include?('foo')
622
+ 'Hello World'
623
+ end
624
+ }
625
+
626
+ get '/bar'
627
+ assert ok?
628
+ assert_equal 'Hello World', body
629
+ end
630
+
631
+ it "transitions to 404 when passed and no subsequent route matches" do
632
+ mock_app {
633
+ get '/:foo' do
634
+ pass
635
+ 'Hello Foo'
636
+ end
637
+ }
638
+
639
+ get '/bar'
640
+ assert not_found?
641
+ end
642
+
643
+ it "transitions to 404 and sets X-Cascade header when passed and no subsequent route matches" do
644
+ mock_app {
645
+ get '/:foo' do
646
+ pass
647
+ 'Hello Foo'
648
+ end
649
+
650
+ get '/bar' do
651
+ 'Hello Bar'
652
+ end
653
+ }
654
+
655
+ get '/foo'
656
+ assert not_found?
657
+ assert_equal 'pass', response.headers['X-Cascade']
658
+ end
659
+
660
+ it "uses optional block passed to pass as route block if no other route is found" do
661
+ mock_app {
662
+ get "/" do
663
+ pass do
664
+ "this"
665
+ end
666
+ "not this"
667
+ end
668
+ }
669
+
670
+ get "/"
671
+ assert ok?
672
+ assert "this", body
673
+ end
674
+
675
+ it "uses optional block passed to pass as route block if no other route is found and superclass has non-matching routes" do
676
+ base = Class.new(Sinatra::Base)
677
+ base.get('/foo') { 'foo in baseclass' }
678
+
679
+ mock_app(base) {
680
+ get "/" do
681
+ pass do
682
+ "this"
683
+ end
684
+ "not this"
685
+ end
686
+ }
687
+
688
+ get "/"
689
+ assert_equal 200, status
690
+ assert "this", body
691
+ end
692
+
693
+ it "passes when matching condition returns false" do
694
+ mock_app {
695
+ condition { params[:foo] == 'bar' }
696
+ get '/:foo' do
697
+ 'Hello World'
698
+ end
699
+ }
700
+
701
+ get '/bar'
702
+ assert ok?
703
+ assert_equal 'Hello World', body
704
+
705
+ get '/foo'
706
+ assert not_found?
707
+ end
708
+
709
+ it "does not pass when matching condition returns nil" do
710
+ mock_app {
711
+ condition { nil }
712
+ get '/:foo' do
713
+ 'Hello World'
714
+ end
715
+ }
716
+
717
+ get '/bar'
718
+ assert ok?
719
+ assert_equal 'Hello World', body
720
+ end
721
+
722
+ it "passes to next route when condition calls pass explicitly" do
723
+ mock_app {
724
+ condition { pass unless params[:foo] == 'bar' }
725
+ get '/:foo' do
726
+ 'Hello World'
727
+ end
728
+ }
729
+
730
+ get '/bar'
731
+ assert ok?
732
+ assert_equal 'Hello World', body
733
+
734
+ get '/foo'
735
+ assert not_found?
736
+ end
737
+
738
+ it "passes to the next route when host_name does not match" do
739
+ mock_app {
740
+ host_name 'example.com'
741
+ get '/foo' do
742
+ 'Hello World'
743
+ end
744
+ }
745
+ get '/foo'
746
+ assert not_found?
747
+
748
+ get '/foo', {}, { 'HTTP_HOST' => 'example.com' }
749
+ assert_equal 200, status
750
+ assert_equal 'Hello World', body
751
+ end
752
+
753
+ it "passes to the next route when user_agent does not match" do
754
+ mock_app {
755
+ user_agent(/Foo/)
756
+ get '/foo' do
757
+ 'Hello World'
758
+ end
759
+ }
760
+ get '/foo'
761
+ assert not_found?
762
+
763
+ get '/foo', {}, { 'HTTP_USER_AGENT' => 'Foo Bar' }
764
+ assert_equal 200, status
765
+ assert_equal 'Hello World', body
766
+ end
767
+
768
+ it "treats missing user agent like an empty string" do
769
+ mock_app do
770
+ user_agent(/.*/)
771
+ get '/' do
772
+ "Hello World"
773
+ end
774
+ end
775
+ get '/'
776
+ assert_equal 200, status
777
+ assert_equal 'Hello World', body
778
+ end
779
+
780
+ it "makes captures in user agent pattern available in params[:agent]" do
781
+ mock_app {
782
+ user_agent(/Foo (.*)/)
783
+ get '/foo' do
784
+ 'Hello ' + params[:agent].first
785
+ end
786
+ }
787
+ get '/foo', {}, { 'HTTP_USER_AGENT' => 'Foo Bar' }
788
+ assert_equal 200, status
789
+ assert_equal 'Hello Bar', body
790
+ end
791
+
792
+ it 'matches mime_types with dots, hyphens and plus signs' do
793
+ mime_types = %w(
794
+ application/atom+xml
795
+ application/ecmascript
796
+ application/EDI-X12
797
+ application/EDIFACT
798
+ application/json
799
+ application/javascript
800
+ application/octet-stream
801
+ application/ogg
802
+ application/pdf
803
+ application/postscript
804
+ application/rdf+xml
805
+ application/rss+xml
806
+ application/soap+xml
807
+ application/font-woff
808
+ application/xhtml+xml
809
+ application/xml
810
+ application/xml-dtd
811
+ application/xop+xml
812
+ application/zip
813
+ application/gzip
814
+ audio/basic
815
+ audio/L24
816
+ audio/mp4
817
+ audio/mpeg
818
+ audio/ogg
819
+ audio/vorbis
820
+ audio/vnd.rn-realaudio
821
+ audio/vnd.wave
822
+ audio/webm
823
+ image/gif
824
+ image/jpeg
825
+ image/pjpeg
826
+ image/png
827
+ image/svg+xml
828
+ image/tiff
829
+ image/vnd.microsoft.icon
830
+ message/http
831
+ message/imdn+xml
832
+ message/partial
833
+ message/rfc822
834
+ model/example
835
+ model/iges
836
+ model/mesh
837
+ model/vrml
838
+ model/x3d+binary
839
+ model/x3d+vrml
840
+ model/x3d+xml
841
+ multipart/mixed
842
+ multipart/alternative
843
+ multipart/related
844
+ multipart/form-data
845
+ multipart/signed
846
+ multipart/encrypted
847
+ text/cmd
848
+ text/css
849
+ text/csv
850
+ text/html
851
+ text/javascript
852
+ application/javascript
853
+ text/plain
854
+ text/vcard
855
+ text/xml
856
+ video/mpeg
857
+ video/mp4
858
+ video/ogg
859
+ video/quicktime
860
+ video/webm
861
+ video/x-matroska
862
+ video/x-ms-wmv
863
+ video/x-flv
864
+ application/vnd.oasis.opendocument.text
865
+ application/vnd.oasis.opendocument.spreadsheet
866
+ application/vnd.oasis.opendocument.presentation
867
+ application/vnd.oasis.opendocument.graphics
868
+ application/vnd.ms-excel
869
+ application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
870
+ application/vnd.ms-powerpoint
871
+ application/vnd.openxmlformats-officedocument.presentationml.presentation
872
+ application/vnd.openxmlformats-officedocument.wordprocessingml.document
873
+ application/vnd.mozilla.xul+xml
874
+ application/vnd.google-earth.kml+xml
875
+ application/x-deb
876
+ application/x-dvi
877
+ application/x-font-ttf
878
+ application/x-javascript
879
+ application/x-latex
880
+ application/x-mpegURL
881
+ application/x-rar-compressed
882
+ application/x-shockwave-flash
883
+ application/x-stuffit
884
+ application/x-tar
885
+ application/x-www-form-urlencoded
886
+ application/x-xpinstall
887
+ audio/x-aac
888
+ audio/x-caf
889
+ image/x-xcf
890
+ text/x-gwt-rpc
891
+ text/x-jquery-tmpl
892
+ application/x-pkcs12
893
+ application/x-pkcs12
894
+ application/x-pkcs7-certificates
895
+ application/x-pkcs7-certificates
896
+ application/x-pkcs7-certreqresp
897
+ application/x-pkcs7-mime
898
+ application/x-pkcs7-mime
899
+ application/x-pkcs7-signature
900
+ )
901
+
902
+ mime_types.each { |mime_type| assert mime_type.match(Sinatra::Request::HEADER_VALUE_WITH_PARAMS) }
903
+ end
904
+
905
+ it "filters by accept header" do
906
+ mock_app {
907
+ get '/', :provides => :xml do
908
+ env['HTTP_ACCEPT']
909
+ end
910
+ get '/foo', :provides => :html do
911
+ env['HTTP_ACCEPT']
912
+ end
913
+ get '/stream', :provides => 'text/event-stream' do
914
+ env['HTTP_ACCEPT']
915
+ end
916
+ }
917
+
918
+ get '/', {}, { 'HTTP_ACCEPT' => 'application/xml' }
919
+ assert ok?
920
+ assert_equal 'application/xml', body
921
+ assert_equal 'application/xml;charset=utf-8', response.headers['Content-Type']
922
+
923
+ get '/', {}, {}
924
+ assert ok?
925
+ assert_equal '', body
926
+ assert_equal 'application/xml;charset=utf-8', response.headers['Content-Type']
927
+
928
+ get '/', {}, { 'HTTP_ACCEPT' => '*/*' }
929
+ assert ok?
930
+ assert_equal '*/*', body
931
+ assert_equal 'application/xml;charset=utf-8', response.headers['Content-Type']
932
+
933
+ get '/', {}, { 'HTTP_ACCEPT' => 'text/html;q=0.9' }
934
+ assert !ok?
935
+
936
+ get '/foo', {}, { 'HTTP_ACCEPT' => 'text/html;q=0.9' }
937
+ assert ok?
938
+ assert_equal 'text/html;q=0.9', body
939
+
940
+ get '/foo', {}, { 'HTTP_ACCEPT' => '' }
941
+ assert ok?
942
+ assert_equal '', body
943
+
944
+ get '/foo', {}, { 'HTTP_ACCEPT' => '*/*' }
945
+ assert ok?
946
+ assert_equal '*/*', body
947
+
948
+ get '/foo', {}, { 'HTTP_ACCEPT' => 'application/xml' }
949
+ assert !ok?
950
+
951
+ get '/stream', {}, { 'HTTP_ACCEPT' => 'text/event-stream' }
952
+ assert ok?
953
+ assert_equal 'text/event-stream', body
954
+
955
+ get '/stream', {}, { 'HTTP_ACCEPT' => '' }
956
+ assert ok?
957
+ assert_equal '', body
958
+
959
+ get '/stream', {}, { 'HTTP_ACCEPT' => '*/*' }
960
+ assert ok?
961
+ assert_equal '*/*', body
962
+
963
+ get '/stream', {}, { 'HTTP_ACCEPT' => 'application/xml' }
964
+ assert !ok?
965
+ end
966
+
967
+ it "filters by current Content-Type" do
968
+ mock_app do
969
+ before('/txt') { content_type :txt }
970
+ get('*', :provides => :txt) { 'txt' }
971
+
972
+ before('/html') { content_type :html }
973
+ get('*', :provides => :html) { 'html' }
974
+ end
975
+
976
+ get '/', {}, { 'HTTP_ACCEPT' => '*/*' }
977
+ assert ok?
978
+ assert_equal 'text/plain;charset=utf-8', response.headers['Content-Type']
979
+ assert_body 'txt'
980
+
981
+ get '/txt', {}, { 'HTTP_ACCEPT' => 'text/plain' }
982
+ assert ok?
983
+ assert_equal 'text/plain;charset=utf-8', response.headers['Content-Type']
984
+ assert_body 'txt'
985
+
986
+ get '/', {}, { 'HTTP_ACCEPT' => 'text/html' }
987
+ assert ok?
988
+ assert_equal 'text/html;charset=utf-8', response.headers['Content-Type']
989
+ assert_body 'html'
990
+ end
991
+
992
+ it "allows multiple mime types for accept header" do
993
+ types = ['image/jpeg', 'image/pjpeg']
994
+
995
+ mock_app {
996
+ get '/', :provides => types do
997
+ env['HTTP_ACCEPT']
998
+ end
999
+ }
1000
+
1001
+ types.each do |type|
1002
+ get '/', {}, { 'HTTP_ACCEPT' => type }
1003
+ assert ok?
1004
+ assert_equal type, body
1005
+ assert_equal type, response.headers['Content-Type']
1006
+ end
1007
+ end
1008
+
1009
+ it 'respects user agent preferences for the content type' do
1010
+ mock_app { get('/', :provides => [:png, :html]) { content_type }}
1011
+ get '/', {}, { 'HTTP_ACCEPT' => 'image/png;q=0.5,text/html;q=0.8' }
1012
+ assert_body 'text/html;charset=utf-8'
1013
+ get '/', {}, { 'HTTP_ACCEPT' => 'image/png;q=0.8,text/html;q=0.5' }
1014
+ assert_body 'image/png'
1015
+ end
1016
+
1017
+ it 'accepts generic types' do
1018
+ mock_app do
1019
+ get('/', :provides => :xml) { content_type }
1020
+ get('/') { 'no match' }
1021
+ end
1022
+ get '/', {}, { 'HTTP_ACCEPT' => 'foo/*' }
1023
+ assert_body 'no match'
1024
+ get '/', {}, { 'HTTP_ACCEPT' => 'application/*' }
1025
+ assert_body 'application/xml;charset=utf-8'
1026
+ get '/', {}, { 'HTTP_ACCEPT' => '*/*' }
1027
+ assert_body 'application/xml;charset=utf-8'
1028
+ end
1029
+
1030
+ it 'prefers concrete over partly generic types' do
1031
+ mock_app { get('/', :provides => [:png, :html]) { content_type }}
1032
+ get '/', {}, { 'HTTP_ACCEPT' => 'image/*, text/html' }
1033
+ assert_body 'text/html;charset=utf-8'
1034
+ get '/', {}, { 'HTTP_ACCEPT' => 'image/png, text/*' }
1035
+ assert_body 'image/png'
1036
+ end
1037
+
1038
+ it 'prefers concrete over fully generic types' do
1039
+ mock_app { get('/', :provides => [:png, :html]) { content_type }}
1040
+ get '/', {}, { 'HTTP_ACCEPT' => '*/*, text/html' }
1041
+ assert_body 'text/html;charset=utf-8'
1042
+ get '/', {}, { 'HTTP_ACCEPT' => 'image/png, */*' }
1043
+ assert_body 'image/png'
1044
+ end
1045
+
1046
+ it 'prefers partly generic over fully generic types' do
1047
+ mock_app { get('/', :provides => [:png, :html]) { content_type }}
1048
+ get '/', {}, { 'HTTP_ACCEPT' => '*/*, text/*' }
1049
+ assert_body 'text/html;charset=utf-8'
1050
+ get '/', {}, { 'HTTP_ACCEPT' => 'image/*, */*' }
1051
+ assert_body 'image/png'
1052
+ end
1053
+
1054
+ it 'respects quality with generic types' do
1055
+ mock_app { get('/', :provides => [:png, :html]) { content_type }}
1056
+ get '/', {}, { 'HTTP_ACCEPT' => 'image/*;q=1, text/html;q=0' }
1057
+ assert_body 'image/png'
1058
+ get '/', {}, { 'HTTP_ACCEPT' => 'image/png;q=0.5, text/*;q=0.7' }
1059
+ assert_body 'text/html;charset=utf-8'
1060
+ end
1061
+
1062
+ it 'supplies a default quality of 1.0' do
1063
+ mock_app { get('/', :provides => [:png, :html]) { content_type }}
1064
+ get '/', {}, { 'HTTP_ACCEPT' => 'image/png;q=0.5, text/*' }
1065
+ assert_body 'text/html;charset=utf-8'
1066
+ end
1067
+
1068
+ it 'orders types with equal quality by parameter count' do
1069
+ mock_app do
1070
+ get('/', :provides => [:png, :jpg]) { content_type }
1071
+ end
1072
+
1073
+ lo_png = 'image/png;q=0.5'
1074
+ hi_png = 'image/png;q=0.5;profile=FOGRA40;gamma=0.8'
1075
+ jpeg = 'image/jpeg;q=0.5;compress=0.25'
1076
+
1077
+ get '/', {}, { 'HTTP_ACCEPT' => "#{lo_png}, #{jpeg}" }
1078
+ assert_body 'image/jpeg'
1079
+ get '/', {}, { 'HTTP_ACCEPT' => "#{hi_png}, #{jpeg}" }
1080
+ assert_body 'image/png'
1081
+ end
1082
+
1083
+ it 'ignores the quality parameter when ordering by parameter count' do
1084
+ mock_app do
1085
+ get('/', :provides => [:png, :jpg]) { content_type }
1086
+ end
1087
+
1088
+ lo_png = 'image/png'
1089
+ hi_png = 'image/png;profile=FOGRA40;gamma=0.8'
1090
+ jpeg = 'image/jpeg;q=1.0;compress=0.25'
1091
+
1092
+ get '/', {}, { 'HTTP_ACCEPT' => "#{jpeg}, #{lo_png}" }
1093
+ assert_body 'image/jpeg'
1094
+ get '/', {}, { 'HTTP_ACCEPT' => "#{jpeg}, #{hi_png}" }
1095
+ assert_body 'image/png'
1096
+ end
1097
+
1098
+ it 'properly handles quoted strings in parameters' do
1099
+ mock_app do
1100
+ get('/', :provides => [:png, :jpg]) { content_type }
1101
+ end
1102
+
1103
+ get '/', {}, { 'HTTP_ACCEPT' => 'image/png;q=0.5;profile=",image/jpeg,"' }
1104
+ assert_body 'image/png'
1105
+ get '/', {}, { 'HTTP_ACCEPT' => 'image/png;q=0.5,image/jpeg;q=0;x=";q=1.0"' }
1106
+ assert_body 'image/png'
1107
+ get '/', {}, { 'HTTP_ACCEPT' => 'image/png;q=0.5,image/jpeg;q=0;x="\";q=1.0"' }
1108
+ assert_body 'image/png'
1109
+ end
1110
+
1111
+ it 'accepts both text/javascript and application/javascript for js' do
1112
+ mock_app { get('/', :provides => :js) { content_type }}
1113
+ get '/', {}, { 'HTTP_ACCEPT' => 'application/javascript' }
1114
+ assert_body 'application/javascript;charset=utf-8'
1115
+ get '/', {}, { 'HTTP_ACCEPT' => 'text/javascript' }
1116
+ assert_body 'text/javascript;charset=utf-8'
1117
+ end
1118
+
1119
+ it 'accepts both text/xml and application/xml for xml' do
1120
+ mock_app { get('/', :provides => :xml) { content_type }}
1121
+ get '/', {}, { 'HTTP_ACCEPT' => 'application/xml' }
1122
+ assert_body 'application/xml;charset=utf-8'
1123
+ get '/', {}, { 'HTTP_ACCEPT' => 'text/xml' }
1124
+ assert_body 'text/xml;charset=utf-8'
1125
+ end
1126
+
1127
+ it 'passes a single url param as block parameters when one param is specified' do
1128
+ mock_app {
1129
+ get '/:foo' do |foo|
1130
+ assert_equal 'bar', foo
1131
+ end
1132
+ }
1133
+
1134
+ get '/bar'
1135
+ assert ok?
1136
+ end
1137
+
1138
+ it 'passes multiple params as block parameters when many are specified' do
1139
+ mock_app {
1140
+ get '/:foo/:bar/:baz' do |foo, bar, baz|
1141
+ assert_equal 'abc', foo
1142
+ assert_equal 'def', bar
1143
+ assert_equal 'ghi', baz
1144
+ end
1145
+ }
1146
+
1147
+ get '/abc/def/ghi'
1148
+ assert ok?
1149
+ end
1150
+
1151
+ it 'passes regular expression captures as block parameters' do
1152
+ mock_app {
1153
+ get(/^\/fo(.*)\/ba(.*)/) do |foo, bar|
1154
+ assert_equal 'orooomma', foo
1155
+ assert_equal 'f', bar
1156
+ 'looks good'
1157
+ end
1158
+ }
1159
+
1160
+ get '/foorooomma/baf'
1161
+ assert ok?
1162
+ assert_equal 'looks good', body
1163
+ end
1164
+
1165
+ it "supports mixing multiple splat params like /*/foo/*/* as block parameters" do
1166
+ mock_app {
1167
+ get '/*/foo/*/*' do |foo, bar, baz|
1168
+ assert_equal 'bar', foo
1169
+ assert_equal 'bling', bar
1170
+ assert_equal 'baz/boom', baz
1171
+ 'looks good'
1172
+ end
1173
+ }
1174
+
1175
+ get '/bar/foo/bling/baz/boom'
1176
+ assert ok?
1177
+ assert_equal 'looks good', body
1178
+ end
1179
+
1180
+ it 'raises an ArgumentError with block arity > 1 and too many values' do
1181
+ mock_app do
1182
+ get '/:foo/:bar/:baz' do |foo, bar|
1183
+ 'quux'
1184
+ end
1185
+ end
1186
+
1187
+ assert_raise(ArgumentError) { get '/a/b/c' }
1188
+ end
1189
+
1190
+ it 'raises an ArgumentError with block param arity > 1 and too few values' do
1191
+ mock_app {
1192
+ get '/:foo/:bar' do |foo, bar, baz|
1193
+ 'quux'
1194
+ end
1195
+ }
1196
+
1197
+ assert_raise(ArgumentError) { get '/a/b' }
1198
+ end
1199
+
1200
+ it 'succeeds if no block parameters are specified' do
1201
+ mock_app {
1202
+ get '/:foo/:bar' do
1203
+ 'quux'
1204
+ end
1205
+ }
1206
+
1207
+ get '/a/b'
1208
+ assert ok?
1209
+ assert_equal 'quux', body
1210
+ end
1211
+
1212
+ it 'passes all params with block param arity -1 (splat args)' do
1213
+ mock_app {
1214
+ get '/:foo/:bar' do |*args|
1215
+ args.join
1216
+ end
1217
+ }
1218
+
1219
+ get '/a/b'
1220
+ assert ok?
1221
+ assert_equal 'ab', body
1222
+ end
1223
+
1224
+ it 'allows custom route-conditions to be set via route options' do
1225
+ protector = Module.new {
1226
+ def protect(*args)
1227
+ condition {
1228
+ unless authorize(params["user"], params["password"])
1229
+ halt 403, "go away"
1230
+ end
1231
+ }
1232
+ end
1233
+ }
1234
+
1235
+ mock_app {
1236
+ register protector
1237
+
1238
+ helpers do
1239
+ def authorize(username, password)
1240
+ username == "foo" && password == "bar"
1241
+ end
1242
+ end
1243
+
1244
+ get "/", :protect => true do
1245
+ "hey"
1246
+ end
1247
+ }
1248
+
1249
+ get "/"
1250
+ assert forbidden?
1251
+ assert_equal "go away", body
1252
+
1253
+ get "/", :user => "foo", :password => "bar"
1254
+ assert ok?
1255
+ assert_equal "hey", body
1256
+ end
1257
+
1258
+ # NOTE Block params behaves differently under 1.8 and 1.9. Under 1.8, block
1259
+ # param arity is lax: declaring a mismatched number of block params results
1260
+ # in a warning. Under 1.9, block param arity is strict: mismatched block
1261
+ # arity raises an ArgumentError.
1262
+
1263
+ if RUBY_VERSION >= '1.9'
1264
+
1265
+ it 'raises an ArgumentError with block param arity 1 and no values' do
1266
+ mock_app {
1267
+ get '/foo' do |foo|
1268
+ 'quux'
1269
+ end
1270
+ }
1271
+
1272
+ assert_raise(ArgumentError) { get '/foo' }
1273
+ end
1274
+
1275
+ it 'raises an ArgumentError with block param arity 1 and too many values' do
1276
+ mock_app {
1277
+ get '/:foo/:bar/:baz' do |foo|
1278
+ 'quux'
1279
+ end
1280
+ }
1281
+
1282
+ assert_raise(ArgumentError) { get '/a/b/c' }
1283
+ end
1284
+
1285
+ else
1286
+
1287
+ it 'does not raise an ArgumentError with block param arity 1 and no values' do
1288
+ mock_app {
1289
+ get '/foo' do |foo|
1290
+ 'quux'
1291
+ end
1292
+ }
1293
+
1294
+ silence_warnings { get '/foo' }
1295
+ assert ok?
1296
+ assert_equal 'quux', body
1297
+ end
1298
+
1299
+ it 'does not raise an ArgumentError with block param arity 1 and too many values' do
1300
+ mock_app {
1301
+ get '/:foo/:bar/:baz' do |foo|
1302
+ 'quux'
1303
+ end
1304
+ }
1305
+
1306
+ silence_warnings { get '/a/b/c' }
1307
+ assert ok?
1308
+ assert_equal 'quux', body
1309
+ end
1310
+
1311
+ end
1312
+
1313
+ it "matches routes defined in superclasses" do
1314
+ base = Class.new(Sinatra::Base)
1315
+ base.get('/foo') { 'foo in baseclass' }
1316
+
1317
+ mock_app(base) {
1318
+ get('/bar') { 'bar in subclass' }
1319
+ }
1320
+
1321
+ get '/foo'
1322
+ assert ok?
1323
+ assert_equal 'foo in baseclass', body
1324
+
1325
+ get '/bar'
1326
+ assert ok?
1327
+ assert_equal 'bar in subclass', body
1328
+ end
1329
+
1330
+ it "matches routes in subclasses before superclasses" do
1331
+ base = Class.new(Sinatra::Base)
1332
+ base.get('/foo') { 'foo in baseclass' }
1333
+ base.get('/bar') { 'bar in baseclass' }
1334
+
1335
+ mock_app(base) {
1336
+ get('/foo') { 'foo in subclass' }
1337
+ }
1338
+
1339
+ get '/foo'
1340
+ assert ok?
1341
+ assert_equal 'foo in subclass', body
1342
+
1343
+ get '/bar'
1344
+ assert ok?
1345
+ assert_equal 'bar in baseclass', body
1346
+ end
1347
+
1348
+ it "adds hostname condition when it is in options" do
1349
+ mock_app {
1350
+ get '/foo', :host => 'host' do
1351
+ 'foo'
1352
+ end
1353
+ }
1354
+
1355
+ get '/foo'
1356
+ assert not_found?
1357
+ end
1358
+
1359
+ it 'allows using call to fire another request internally' do
1360
+ mock_app do
1361
+ get '/foo' do
1362
+ status, headers, body = call env.merge("PATH_INFO" => '/bar')
1363
+ [status, headers, body.each.map(&:upcase)]
1364
+ end
1365
+
1366
+ get '/bar' do
1367
+ "bar"
1368
+ end
1369
+ end
1370
+
1371
+ get '/foo'
1372
+ assert ok?
1373
+ assert_body "BAR"
1374
+ end
1375
+
1376
+ it 'plays well with other routing middleware' do
1377
+ middleware = Sinatra.new
1378
+ inner_app = Sinatra.new { get('/foo') { 'hello' } }
1379
+ builder = Rack::Builder.new do
1380
+ use middleware
1381
+ map('/test') { run inner_app }
1382
+ end
1383
+
1384
+ @app = builder.to_app
1385
+ get '/test/foo'
1386
+ assert ok?
1387
+ assert_body 'hello'
1388
+ end
1389
+
1390
+ it 'returns the route signature' do
1391
+ signature = list = nil
1392
+
1393
+ mock_app do
1394
+ signature = post('/') { }
1395
+ list = routes['POST']
1396
+ end
1397
+
1398
+ assert_equal Array, signature.class
1399
+ assert_equal 4, signature.length
1400
+ assert list.include?(signature)
1401
+ end
1402
+
1403
+ it "sets env['sinatra.route'] to the matched route" do
1404
+ mock_app do
1405
+ after do
1406
+ assert_equal 'GET /users/:id/status', env['sinatra.route']
1407
+ end
1408
+ get('/users/:id/status') { 'ok' }
1409
+ end
1410
+ get '/users/1/status'
1411
+ end
1412
+ end