devcenter 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. data/.gitignore +18 -0
  2. data/Gemfile +8 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +39 -0
  5. data/Rakefile +2 -0
  6. data/bin/devcenter +7 -0
  7. data/devcenter.gemspec +30 -0
  8. data/lib/devcenter.rb +6 -0
  9. data/lib/devcenter/cli.rb +45 -0
  10. data/lib/devcenter/coderay_extensions.rb +70 -0
  11. data/lib/devcenter/commands.rb +4 -0
  12. data/lib/devcenter/commands/base.rb +47 -0
  13. data/lib/devcenter/commands/open.rb +34 -0
  14. data/lib/devcenter/commands/preview.rb +28 -0
  15. data/lib/devcenter/commands/pull.rb +37 -0
  16. data/lib/devcenter/helpers.rb +41 -0
  17. data/lib/devcenter/layout.html +299 -0
  18. data/lib/devcenter/md_parser.rb +87 -0
  19. data/lib/devcenter/previewer.rb +36 -0
  20. data/lib/devcenter/previewer/assets/images/public/article-icon-large.png +0 -0
  21. data/lib/devcenter/previewer/assets/images/public/article-icon.png +0 -0
  22. data/lib/devcenter/previewer/assets/images/public/aside_accordion_indicator_default.png +0 -0
  23. data/lib/devcenter/previewer/assets/images/public/aside_accordion_indicator_open.png +0 -0
  24. data/lib/devcenter/previewer/assets/images/public/body_bg.png +0 -0
  25. data/lib/devcenter/previewer/assets/images/public/callout_bg.png +0 -0
  26. data/lib/devcenter/previewer/assets/images/public/feed-icon-sprite.png +0 -0
  27. data/lib/devcenter/previewer/assets/images/public/heroku-header-logo-mobile.png +0 -0
  28. data/lib/devcenter/previewer/assets/images/public/heroku-header-logo.png +0 -0
  29. data/lib/devcenter/previewer/assets/images/public/heroku-logo.png +0 -0
  30. data/lib/devcenter/previewer/assets/images/public/icon_sprite_16.png +0 -0
  31. data/lib/devcenter/previewer/assets/images/public/index_li_bullet.png +0 -0
  32. data/lib/devcenter/previewer/assets/images/public/intro_bg.png +0 -0
  33. data/lib/devcenter/previewer/assets/images/public/jive_discussion_arrow.png +0 -0
  34. data/lib/devcenter/previewer/assets/images/public/jive_discussion_glyph.png +0 -0
  35. data/lib/devcenter/previewer/assets/images/public/line.png +0 -0
  36. data/lib/devcenter/previewer/assets/images/public/pre_code_background.png +0 -0
  37. data/lib/devcenter/previewer/assets/images/public/search-icon.png +0 -0
  38. data/lib/devcenter/previewer/assets/images/public/search_glyph.png +0 -0
  39. data/lib/devcenter/previewer/assets/images/public/search_return.png +0 -0
  40. data/lib/devcenter/previewer/assets/images/public/tag-icon-large.png +0 -0
  41. data/lib/devcenter/previewer/assets/images/public/toc-icon.png +0 -0
  42. data/lib/devcenter/previewer/assets/public.css +2125 -0
  43. data/lib/devcenter/previewer/assets/public/article-icon-large.png +0 -0
  44. data/lib/devcenter/previewer/assets/public/article-icon.png +0 -0
  45. data/lib/devcenter/previewer/assets/public/aside_accordion_indicator_default.png +0 -0
  46. data/lib/devcenter/previewer/assets/public/aside_accordion_indicator_open.png +0 -0
  47. data/lib/devcenter/previewer/assets/public/body_bg.png +0 -0
  48. data/lib/devcenter/previewer/assets/public/callout_bg.png +0 -0
  49. data/lib/devcenter/previewer/assets/public/feed-icon-sprite.png +0 -0
  50. data/lib/devcenter/previewer/assets/public/heroku-header-logo-mobile.png +0 -0
  51. data/lib/devcenter/previewer/assets/public/heroku-header-logo.png +0 -0
  52. data/lib/devcenter/previewer/assets/public/heroku-logo.png +0 -0
  53. data/lib/devcenter/previewer/assets/public/icon_sprite_16.png +0 -0
  54. data/lib/devcenter/previewer/assets/public/index_li_bullet.png +0 -0
  55. data/lib/devcenter/previewer/assets/public/intro_bg.png +0 -0
  56. data/lib/devcenter/previewer/assets/public/jive_discussion_arrow.png +0 -0
  57. data/lib/devcenter/previewer/assets/public/jive_discussion_glyph.png +0 -0
  58. data/lib/devcenter/previewer/assets/public/line.png +0 -0
  59. data/lib/devcenter/previewer/assets/public/pre_code_background.png +0 -0
  60. data/lib/devcenter/previewer/assets/public/public.css +2125 -0
  61. data/lib/devcenter/previewer/assets/public/search-icon.png +0 -0
  62. data/lib/devcenter/previewer/assets/public/search_glyph.png +0 -0
  63. data/lib/devcenter/previewer/assets/public/search_return.png +0 -0
  64. data/lib/devcenter/previewer/assets/public/tag-icon-large.png +0 -0
  65. data/lib/devcenter/previewer/assets/public/toc-icon.png +0 -0
  66. data/lib/devcenter/previewer/file_listener.rb +23 -0
  67. data/lib/devcenter/previewer/views/article.erb +345 -0
  68. data/lib/devcenter/previewer/web_app.rb +53 -0
  69. data/lib/devcenter/previewer/web_server.rb +29 -0
  70. data/lib/devcenter/version.rb +3 -0
  71. data/vendor/sinatra/.gitignore +6 -0
  72. data/vendor/sinatra/.travis.yml +16 -0
  73. data/vendor/sinatra/.yardopts +4 -0
  74. data/vendor/sinatra/AUTHORS +61 -0
  75. data/vendor/sinatra/Gemfile +91 -0
  76. data/vendor/sinatra/LICENSE +22 -0
  77. data/vendor/sinatra/README.de.rdoc +2116 -0
  78. data/vendor/sinatra/README.es.rdoc +2106 -0
  79. data/vendor/sinatra/README.fr.rdoc +2133 -0
  80. data/vendor/sinatra/README.hu.rdoc +608 -0
  81. data/vendor/sinatra/README.jp.rdoc +1056 -0
  82. data/vendor/sinatra/README.ko.rdoc +1932 -0
  83. data/vendor/sinatra/README.pt-br.rdoc +778 -0
  84. data/vendor/sinatra/README.pt-pt.rdoc +647 -0
  85. data/vendor/sinatra/README.rdoc +2049 -0
  86. data/vendor/sinatra/README.ru.rdoc +2033 -0
  87. data/vendor/sinatra/README.zh.rdoc +1816 -0
  88. data/vendor/sinatra/Rakefile +182 -0
  89. data/vendor/sinatra/examples/chat.rb +61 -0
  90. data/vendor/sinatra/examples/simple.rb +3 -0
  91. data/vendor/sinatra/examples/stream.ru +26 -0
  92. data/vendor/sinatra/lib/sinatra.rb +5 -0
  93. data/vendor/sinatra/lib/sinatra/base.rb +1820 -0
  94. data/vendor/sinatra/lib/sinatra/images/404.png +0 -0
  95. data/vendor/sinatra/lib/sinatra/images/500.png +0 -0
  96. data/vendor/sinatra/lib/sinatra/main.rb +30 -0
  97. data/vendor/sinatra/lib/sinatra/showexceptions.rb +345 -0
  98. data/vendor/sinatra/lib/sinatra/version.rb +3 -0
  99. data/vendor/sinatra/sinatra.gemspec +18 -0
  100. data/vendor/sinatra/test/base_test.rb +172 -0
  101. data/vendor/sinatra/test/builder_test.rb +91 -0
  102. data/vendor/sinatra/test/coffee_test.rb +90 -0
  103. data/vendor/sinatra/test/compile_test.rb +139 -0
  104. data/vendor/sinatra/test/contest.rb +98 -0
  105. data/vendor/sinatra/test/creole_test.rb +65 -0
  106. data/vendor/sinatra/test/delegator_test.rb +160 -0
  107. data/vendor/sinatra/test/encoding_test.rb +20 -0
  108. data/vendor/sinatra/test/erb_test.rb +98 -0
  109. data/vendor/sinatra/test/extensions_test.rb +98 -0
  110. data/vendor/sinatra/test/filter_test.rb +437 -0
  111. data/vendor/sinatra/test/haml_test.rb +91 -0
  112. data/vendor/sinatra/test/helper.rb +123 -0
  113. data/vendor/sinatra/test/helpers_test.rb +1768 -0
  114. data/vendor/sinatra/test/integration/app.rb +62 -0
  115. data/vendor/sinatra/test/integration_helper.rb +222 -0
  116. data/vendor/sinatra/test/integration_test.rb +87 -0
  117. data/vendor/sinatra/test/less_test.rb +69 -0
  118. data/vendor/sinatra/test/liquid_test.rb +59 -0
  119. data/vendor/sinatra/test/mapped_error_test.rb +305 -0
  120. data/vendor/sinatra/test/markaby_test.rb +80 -0
  121. data/vendor/sinatra/test/markdown_test.rb +82 -0
  122. data/vendor/sinatra/test/middleware_test.rb +68 -0
  123. data/vendor/sinatra/test/nokogiri_test.rb +67 -0
  124. data/vendor/sinatra/test/public/favicon.ico +0 -0
  125. data/vendor/sinatra/test/rabl_test.rb +89 -0
  126. data/vendor/sinatra/test/rack_test.rb +45 -0
  127. data/vendor/sinatra/test/radius_test.rb +59 -0
  128. data/vendor/sinatra/test/rdoc_test.rb +66 -0
  129. data/vendor/sinatra/test/readme_test.rb +120 -0
  130. data/vendor/sinatra/test/request_test.rb +45 -0
  131. data/vendor/sinatra/test/response_test.rb +64 -0
  132. data/vendor/sinatra/test/result_test.rb +76 -0
  133. data/vendor/sinatra/test/route_added_hook_test.rb +59 -0
  134. data/vendor/sinatra/test/routing_test.rb +1175 -0
  135. data/vendor/sinatra/test/sass_test.rb +116 -0
  136. data/vendor/sinatra/test/scss_test.rb +89 -0
  137. data/vendor/sinatra/test/server_test.rb +48 -0
  138. data/vendor/sinatra/test/settings_test.rb +561 -0
  139. data/vendor/sinatra/test/sinatra_test.rb +12 -0
  140. data/vendor/sinatra/test/slim_test.rb +84 -0
  141. data/vendor/sinatra/test/static_test.rb +219 -0
  142. data/vendor/sinatra/test/streaming_test.rb +149 -0
  143. data/vendor/sinatra/test/templates_test.rb +333 -0
  144. data/vendor/sinatra/test/textile_test.rb +65 -0
  145. data/vendor/sinatra/test/views/a/in_a.str +1 -0
  146. data/vendor/sinatra/test/views/ascii.erb +2 -0
  147. data/vendor/sinatra/test/views/b/in_b.str +1 -0
  148. data/vendor/sinatra/test/views/calc.html.erb +1 -0
  149. data/vendor/sinatra/test/views/error.builder +3 -0
  150. data/vendor/sinatra/test/views/error.erb +3 -0
  151. data/vendor/sinatra/test/views/error.haml +3 -0
  152. data/vendor/sinatra/test/views/error.sass +2 -0
  153. data/vendor/sinatra/test/views/explicitly_nested.str +1 -0
  154. data/vendor/sinatra/test/views/foo/hello.test +1 -0
  155. data/vendor/sinatra/test/views/hello.builder +1 -0
  156. data/vendor/sinatra/test/views/hello.coffee +1 -0
  157. data/vendor/sinatra/test/views/hello.creole +1 -0
  158. data/vendor/sinatra/test/views/hello.erb +1 -0
  159. data/vendor/sinatra/test/views/hello.haml +1 -0
  160. data/vendor/sinatra/test/views/hello.less +5 -0
  161. data/vendor/sinatra/test/views/hello.liquid +1 -0
  162. data/vendor/sinatra/test/views/hello.mab +1 -0
  163. data/vendor/sinatra/test/views/hello.md +1 -0
  164. data/vendor/sinatra/test/views/hello.nokogiri +1 -0
  165. data/vendor/sinatra/test/views/hello.rabl +2 -0
  166. data/vendor/sinatra/test/views/hello.radius +1 -0
  167. data/vendor/sinatra/test/views/hello.rdoc +1 -0
  168. data/vendor/sinatra/test/views/hello.sass +2 -0
  169. data/vendor/sinatra/test/views/hello.scss +3 -0
  170. data/vendor/sinatra/test/views/hello.slim +1 -0
  171. data/vendor/sinatra/test/views/hello.str +1 -0
  172. data/vendor/sinatra/test/views/hello.test +1 -0
  173. data/vendor/sinatra/test/views/hello.textile +1 -0
  174. data/vendor/sinatra/test/views/hello.wlang +1 -0
  175. data/vendor/sinatra/test/views/hello.yajl +1 -0
  176. data/vendor/sinatra/test/views/layout2.builder +3 -0
  177. data/vendor/sinatra/test/views/layout2.erb +2 -0
  178. data/vendor/sinatra/test/views/layout2.haml +2 -0
  179. data/vendor/sinatra/test/views/layout2.liquid +2 -0
  180. data/vendor/sinatra/test/views/layout2.mab +2 -0
  181. data/vendor/sinatra/test/views/layout2.nokogiri +3 -0
  182. data/vendor/sinatra/test/views/layout2.rabl +3 -0
  183. data/vendor/sinatra/test/views/layout2.radius +2 -0
  184. data/vendor/sinatra/test/views/layout2.slim +3 -0
  185. data/vendor/sinatra/test/views/layout2.str +2 -0
  186. data/vendor/sinatra/test/views/layout2.test +1 -0
  187. data/vendor/sinatra/test/views/layout2.wlang +2 -0
  188. data/vendor/sinatra/test/views/nested.str +1 -0
  189. data/vendor/sinatra/test/views/utf8.erb +2 -0
  190. data/vendor/sinatra/test/wlang_test.rb +70 -0
  191. data/vendor/sinatra/test/yajl_test.rb +86 -0
  192. metadata +414 -0
@@ -0,0 +1,45 @@
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
+ params = 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
+ end
@@ -0,0 +1,64 @@
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
+ enum = Enumerable.const_get(:Enumerator)
10
+ assert_equal enum.new(a).to_a, enum.new(b).to_a
11
+ end
12
+
13
+ it "initializes with 200, text/html, and empty body" do
14
+ assert_equal 200, @response.status
15
+ assert_equal 'text/html', @response['Content-Type']
16
+ assert_equal [], @response.body
17
+ end
18
+
19
+ it 'uses case insensitive headers' do
20
+ @response['content-type'] = 'application/foo'
21
+ assert_equal 'application/foo', @response['Content-Type']
22
+ assert_equal 'application/foo', @response['CONTENT-TYPE']
23
+ end
24
+
25
+ it 'writes to body' do
26
+ @response.body = 'Hello'
27
+ @response.write ' World'
28
+ assert_equal 'Hello World', @response.body.join
29
+ end
30
+
31
+ [204, 304].each do |status_code|
32
+ it "removes the Content-Type header and body when response status is #{status_code}" do
33
+ @response.status = status_code
34
+ @response.body = ['Hello World']
35
+ assert_equal [status_code, {}, []], @response.finish
36
+ end
37
+ end
38
+
39
+ it 'Calculates the Content-Length using the bytesize of the body' do
40
+ @response.body = ['Hello', 'World!', '✈']
41
+ status, headers, body = @response.finish
42
+ assert_equal '14', headers['Content-Length']
43
+ assert_same_body @response.body, body
44
+ end
45
+
46
+ it 'does not call #to_ary or #inject on the body' do
47
+ object = Object.new
48
+ def object.inject(*) fail 'called' end
49
+ def object.to_ary(*) fail 'called' end
50
+ def object.each(*) end
51
+ @response.body = object
52
+ assert @response.finish
53
+ end
54
+
55
+ it 'does not nest a Sinatra::Response' do
56
+ @response.body = Sinatra::Response.new ["foo"]
57
+ assert_same_body @response.body, ["foo"]
58
+ end
59
+
60
+ it 'does not nest a Rack::Response' do
61
+ @response.body = Rack::Response.new ["foo"]
62
+ assert_same_body @response.body, ["foo"]
63
+ end
64
+ 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,1175 @@
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].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 "allows using unicode" do
74
+ mock_app do
75
+ get('/föö') { }
76
+ end
77
+ get '/f%C3%B6%C3%B6'
78
+ assert_equal 200, status
79
+ end
80
+
81
+ it "it handles encoded slashes correctly" do
82
+ mock_app { get("/:a") { |a| a } }
83
+ get '/foo%2Fbar'
84
+ assert_equal 200, status
85
+ assert_body "foo/bar"
86
+ end
87
+
88
+ it "overrides the content-type in error handlers" do
89
+ mock_app {
90
+ before { content_type 'text/plain' }
91
+ error Sinatra::NotFound do
92
+ content_type "text/html"
93
+ "<h1>Not Found</h1>"
94
+ end
95
+ }
96
+
97
+ get '/foo'
98
+ assert_equal 404, status
99
+ assert_equal 'text/html;charset=utf-8', response["Content-Type"]
100
+ assert_equal "<h1>Not Found</h1>", response.body
101
+ end
102
+
103
+ it 'matches empty PATH_INFO to "/" if no route is defined for ""' do
104
+ mock_app do
105
+ get '/' do
106
+ 'worked'
107
+ end
108
+ end
109
+
110
+ get '/', {}, "PATH_INFO" => ""
111
+ assert ok?
112
+ assert_equal 'worked', body
113
+ end
114
+
115
+ it 'matches empty PATH_INFO to "" if a route is defined for ""' do
116
+ mock_app do
117
+ disable :protection
118
+
119
+ get '/' do
120
+ 'did not work'
121
+ end
122
+
123
+ get '' do
124
+ 'worked'
125
+ end
126
+ end
127
+
128
+ get '/', {}, "PATH_INFO" => ""
129
+ assert ok?
130
+ assert_equal 'worked', body
131
+ end
132
+
133
+ it 'takes multiple definitions of a route' do
134
+ mock_app {
135
+ user_agent(/Foo/)
136
+ get '/foo' do
137
+ 'foo'
138
+ end
139
+
140
+ get '/foo' do
141
+ 'not foo'
142
+ end
143
+ }
144
+
145
+ get '/foo', {}, 'HTTP_USER_AGENT' => 'Foo'
146
+ assert ok?
147
+ assert_equal 'foo', body
148
+
149
+ get '/foo'
150
+ assert ok?
151
+ assert_equal 'not foo', body
152
+ end
153
+
154
+ it "exposes params with indifferent hash" do
155
+ mock_app {
156
+ get '/:foo' do
157
+ assert_equal 'bar', params['foo']
158
+ assert_equal 'bar', params[:foo]
159
+ 'well, alright'
160
+ end
161
+ }
162
+ get '/bar'
163
+ assert_equal 'well, alright', body
164
+ end
165
+
166
+ it "merges named params and query string params in params" do
167
+ mock_app {
168
+ get '/:foo' do
169
+ assert_equal 'bar', params['foo']
170
+ assert_equal 'biz', params['baz']
171
+ end
172
+ }
173
+ get '/bar?baz=biz'
174
+ assert ok?
175
+ end
176
+
177
+ it "supports named params like /hello/:person" do
178
+ mock_app {
179
+ get '/hello/:person' do
180
+ "Hello #{params['person']}"
181
+ end
182
+ }
183
+ get '/hello/Frank'
184
+ assert_equal 'Hello Frank', body
185
+ end
186
+
187
+ it "supports optional named params like /?:foo?/?:bar?" do
188
+ mock_app {
189
+ get '/?:foo?/?:bar?' do
190
+ "foo=#{params[:foo]};bar=#{params[:bar]}"
191
+ end
192
+ }
193
+
194
+ get '/hello/world'
195
+ assert ok?
196
+ assert_equal "foo=hello;bar=world", body
197
+
198
+ get '/hello'
199
+ assert ok?
200
+ assert_equal "foo=hello;bar=", body
201
+
202
+ get '/'
203
+ assert ok?
204
+ assert_equal "foo=;bar=", body
205
+ end
206
+
207
+ it "supports named captures like %r{/hello/(?<person>[^/?#]+)} on Ruby >= 1.9" do
208
+ next if RUBY_VERSION < '1.9'
209
+ mock_app {
210
+ get Regexp.new('/hello/(?<person>[^/?#]+)') do
211
+ "Hello #{params['person']}"
212
+ end
213
+ }
214
+ get '/hello/Frank'
215
+ assert_equal 'Hello Frank', body
216
+ end
217
+
218
+ it "supports optional named captures like %r{/page(?<format>.[^/?#]+)?} on Ruby >= 1.9" do
219
+ next if RUBY_VERSION < '1.9'
220
+ mock_app {
221
+ get Regexp.new('/page(?<format>.[^/?#]+)?') do
222
+ "format=#{params[:format]}"
223
+ end
224
+ }
225
+
226
+ get '/page.html'
227
+ assert ok?
228
+ assert_equal "format=.html", body
229
+
230
+ get '/page.xml'
231
+ assert ok?
232
+ assert_equal "format=.xml", body
233
+
234
+ get '/page'
235
+ assert ok?
236
+ assert_equal "format=", body
237
+ end
238
+
239
+ it 'does not concatinate params with the same name' do
240
+ mock_app { get('/:foo') { params[:foo] } }
241
+ get '/a?foo=b'
242
+ assert_body 'a'
243
+ end
244
+
245
+ it "supports single splat params like /*" do
246
+ mock_app {
247
+ get '/*' do
248
+ assert params['splat'].kind_of?(Array)
249
+ params['splat'].join "\n"
250
+ end
251
+ }
252
+
253
+ get '/foo'
254
+ assert_equal "foo", body
255
+
256
+ get '/foo/bar/baz'
257
+ assert_equal "foo/bar/baz", body
258
+ end
259
+
260
+ it "supports mixing multiple splat params like /*/foo/*/*" do
261
+ mock_app {
262
+ get '/*/foo/*/*' do
263
+ assert params['splat'].kind_of?(Array)
264
+ params['splat'].join "\n"
265
+ end
266
+ }
267
+
268
+ get '/bar/foo/bling/baz/boom'
269
+ assert_equal "bar\nbling\nbaz/boom", body
270
+
271
+ get '/bar/foo/baz'
272
+ assert not_found?
273
+ end
274
+
275
+ it "supports mixing named and splat params like /:foo/*" do
276
+ mock_app {
277
+ get '/:foo/*' do
278
+ assert_equal 'foo', params['foo']
279
+ assert_equal ['bar/baz'], params['splat']
280
+ end
281
+ }
282
+
283
+ get '/foo/bar/baz'
284
+ assert ok?
285
+ end
286
+
287
+ it "matches a dot ('.') as part of a named param" do
288
+ mock_app {
289
+ get '/:foo/:bar' do
290
+ params[:foo]
291
+ end
292
+ }
293
+
294
+ get '/user@example.com/name'
295
+ assert_equal 200, response.status
296
+ assert_equal 'user@example.com', body
297
+ end
298
+
299
+ it "matches a literal dot ('.') outside of named params" do
300
+ mock_app {
301
+ get '/:file.:ext' do
302
+ assert_equal 'pony', params[:file]
303
+ assert_equal 'jpg', params[:ext]
304
+ 'right on'
305
+ end
306
+ }
307
+
308
+ get '/pony.jpg'
309
+ assert_equal 200, response.status
310
+ assert_equal 'right on', body
311
+ end
312
+
313
+ it "literally matches dot in paths" do
314
+ route_def '/test.bar'
315
+
316
+ get '/test.bar'
317
+ assert ok?
318
+ get 'test0bar'
319
+ assert not_found?
320
+ end
321
+
322
+ it "literally matches dollar sign in paths" do
323
+ route_def '/test$/'
324
+
325
+ get '/test$/'
326
+ assert ok?
327
+ end
328
+
329
+ it "literally matches plus sign in paths" do
330
+ route_def '/te+st/'
331
+
332
+ get '/te%2Bst/'
333
+ assert ok?
334
+ get '/teeeeeeest/'
335
+ assert not_found?
336
+ end
337
+
338
+ it "converts plus sign into space as the value of a named param" do
339
+ mock_app {
340
+ get '/:test' do
341
+ params["test"]
342
+ end
343
+ }
344
+ get '/bob+ross'
345
+ assert ok?
346
+ assert_equal 'bob ross', body
347
+ end
348
+
349
+ it "literally matches parens in paths" do
350
+ route_def '/test(bar)/'
351
+
352
+ get '/test(bar)/'
353
+ assert ok?
354
+ end
355
+
356
+ it "supports basic nested params" do
357
+ mock_app {
358
+ get '/hi' do
359
+ params["person"]["name"]
360
+ end
361
+ }
362
+
363
+ get "/hi?person[name]=John+Doe"
364
+ assert ok?
365
+ assert_equal "John Doe", body
366
+ end
367
+
368
+ it "exposes nested params with indifferent hash" do
369
+ mock_app {
370
+ get '/testme' do
371
+ assert_equal 'baz', params['bar']['foo']
372
+ assert_equal 'baz', params['bar'][:foo]
373
+ 'well, alright'
374
+ end
375
+ }
376
+ get '/testme?bar[foo]=baz'
377
+ assert_equal 'well, alright', body
378
+ end
379
+
380
+ it "exposes params nested within arrays with indifferent hash" do
381
+ mock_app {
382
+ get '/testme' do
383
+ assert_equal 'baz', params['bar'][0]['foo']
384
+ assert_equal 'baz', params['bar'][0][:foo]
385
+ 'well, alright'
386
+ end
387
+ }
388
+ get '/testme?bar[][foo]=baz'
389
+ assert_equal 'well, alright', body
390
+ end
391
+
392
+ it "supports arrays within params" do
393
+ mock_app {
394
+ get '/foo' do
395
+ assert_equal ['A', 'B'], params['bar']
396
+ 'looks good'
397
+ end
398
+ }
399
+ get '/foo?bar[]=A&bar[]=B'
400
+ assert ok?
401
+ assert_equal 'looks good', body
402
+ end
403
+
404
+ it "supports deeply nested params" do
405
+ expected_params = {
406
+ "emacs" => {
407
+ "map" => { "goto-line" => "M-g g" },
408
+ "version" => "22.3.1"
409
+ },
410
+ "browser" => {
411
+ "firefox" => {"engine" => {"name"=>"spidermonkey", "version"=>"1.7.0"}},
412
+ "chrome" => {"engine" => {"name"=>"V8", "version"=>"1.0"}}
413
+ },
414
+ "paste" => {"name"=>"hello world", "syntax"=>"ruby"}
415
+ }
416
+ mock_app {
417
+ get '/foo' do
418
+ assert_equal expected_params, params
419
+ 'looks good'
420
+ end
421
+ }
422
+ get '/foo', expected_params
423
+ assert ok?
424
+ assert_equal 'looks good', body
425
+ end
426
+
427
+ it "preserves non-nested params" do
428
+ mock_app {
429
+ get '/foo' do
430
+ assert_equal "2", params["article_id"]
431
+ assert_equal "awesome", params['comment']['body']
432
+ assert_nil params['comment[body]']
433
+ 'looks good'
434
+ end
435
+ }
436
+
437
+ get '/foo?article_id=2&comment[body]=awesome'
438
+ assert ok?
439
+ assert_equal 'looks good', body
440
+ end
441
+
442
+ it "matches paths that include spaces encoded with %20" do
443
+ mock_app {
444
+ get '/path with spaces' do
445
+ 'looks good'
446
+ end
447
+ }
448
+
449
+ get '/path%20with%20spaces'
450
+ assert ok?
451
+ assert_equal 'looks good', body
452
+ end
453
+
454
+ it "matches paths that include spaces encoded with +" do
455
+ mock_app {
456
+ get '/path with spaces' do
457
+ 'looks good'
458
+ end
459
+ }
460
+
461
+ get '/path+with+spaces'
462
+ assert ok?
463
+ assert_equal 'looks good', body
464
+ end
465
+
466
+ it "matches paths that include ampersands" do
467
+ mock_app {
468
+ get '/:name' do
469
+ 'looks good'
470
+ end
471
+ }
472
+
473
+ get '/foo&bar'
474
+ assert ok?
475
+ assert_equal 'looks good', body
476
+ end
477
+
478
+ it "URL decodes named parameters and splats" do
479
+ mock_app {
480
+ get '/:foo/*' do
481
+ assert_equal 'hello world', params['foo']
482
+ assert_equal ['how are you'], params['splat']
483
+ nil
484
+ end
485
+ }
486
+
487
+ get '/hello%20world/how%20are%20you'
488
+ assert ok?
489
+ end
490
+
491
+ it 'supports regular expressions' do
492
+ mock_app {
493
+ get(/^\/foo...\/bar$/) do
494
+ 'Hello World'
495
+ end
496
+ }
497
+
498
+ get '/foooom/bar'
499
+ assert ok?
500
+ assert_equal 'Hello World', body
501
+ end
502
+
503
+ it 'makes regular expression captures available in params[:captures]' do
504
+ mock_app {
505
+ get(/^\/fo(.*)\/ba(.*)/) do
506
+ assert_equal ['orooomma', 'f'], params[:captures]
507
+ 'right on'
508
+ end
509
+ }
510
+
511
+ get '/foorooomma/baf'
512
+ assert ok?
513
+ assert_equal 'right on', body
514
+ end
515
+
516
+ it 'supports regular expression look-alike routes' do
517
+ mock_app {
518
+ get(RegexpLookAlike.new) do
519
+ assert_equal 'this', params[:one]
520
+ assert_equal 'is', params[:two]
521
+ assert_equal 'a', params[:three]
522
+ assert_equal 'test', params[:four]
523
+ 'right on'
524
+ end
525
+ }
526
+
527
+ get '/this/is/a/test/'
528
+ assert ok?
529
+ assert_equal 'right on', body
530
+ end
531
+
532
+ it 'raises a TypeError when pattern is not a String or Regexp' do
533
+ assert_raise(TypeError) {
534
+ mock_app { get(42){} }
535
+ }
536
+ end
537
+
538
+ it "returns response immediately on halt" do
539
+ mock_app {
540
+ get '/' do
541
+ halt 'Hello World'
542
+ 'Boo-hoo World'
543
+ end
544
+ }
545
+
546
+ get '/'
547
+ assert ok?
548
+ assert_equal 'Hello World', body
549
+ end
550
+
551
+ it "halts with a response tuple" do
552
+ mock_app {
553
+ get '/' do
554
+ halt 295, {'Content-Type' => 'text/plain'}, 'Hello World'
555
+ end
556
+ }
557
+
558
+ get '/'
559
+ assert_equal 295, status
560
+ assert_equal 'text/plain', response['Content-Type']
561
+ assert_equal 'Hello World', body
562
+ end
563
+
564
+ it "halts with an array of strings" do
565
+ mock_app {
566
+ get '/' do
567
+ halt %w[Hello World How Are You]
568
+ end
569
+ }
570
+
571
+ get '/'
572
+ assert_equal 'HelloWorldHowAreYou', body
573
+ end
574
+
575
+ it 'sets response.status with halt' do
576
+ status_was = nil
577
+ mock_app do
578
+ after { status_was = status }
579
+ get('/') { halt 500, 'error' }
580
+ end
581
+ get '/'
582
+ assert_status 500
583
+ assert_equal 500, status_was
584
+ end
585
+
586
+ it "transitions to the next matching route on pass" do
587
+ mock_app {
588
+ get '/:foo' do
589
+ pass
590
+ 'Hello Foo'
591
+ end
592
+
593
+ get '/*' do
594
+ assert !params.include?('foo')
595
+ 'Hello World'
596
+ end
597
+ }
598
+
599
+ get '/bar'
600
+ assert ok?
601
+ assert_equal 'Hello World', body
602
+ end
603
+
604
+ it "transitions to 404 when passed and no subsequent route matches" do
605
+ mock_app {
606
+ get '/:foo' do
607
+ pass
608
+ 'Hello Foo'
609
+ end
610
+ }
611
+
612
+ get '/bar'
613
+ assert not_found?
614
+ end
615
+
616
+ it "transitions to 404 and sets X-Cascade header when passed and no subsequent route matches" do
617
+ mock_app {
618
+ get '/:foo' do
619
+ pass
620
+ 'Hello Foo'
621
+ end
622
+
623
+ get '/bar' do
624
+ 'Hello Bar'
625
+ end
626
+ }
627
+
628
+ get '/foo'
629
+ assert not_found?
630
+ assert_equal 'pass', response.headers['X-Cascade']
631
+ end
632
+
633
+ it "uses optional block passed to pass as route block if no other route is found" do
634
+ mock_app {
635
+ get "/" do
636
+ pass do
637
+ "this"
638
+ end
639
+ "not this"
640
+ end
641
+ }
642
+
643
+ get "/"
644
+ assert ok?
645
+ assert "this", body
646
+ end
647
+
648
+ it "passes when matching condition returns false" do
649
+ mock_app {
650
+ condition { params[:foo] == 'bar' }
651
+ get '/:foo' do
652
+ 'Hello World'
653
+ end
654
+ }
655
+
656
+ get '/bar'
657
+ assert ok?
658
+ assert_equal 'Hello World', body
659
+
660
+ get '/foo'
661
+ assert not_found?
662
+ end
663
+
664
+ it "does not pass when matching condition returns nil" do
665
+ mock_app {
666
+ condition { nil }
667
+ get '/:foo' do
668
+ 'Hello World'
669
+ end
670
+ }
671
+
672
+ get '/bar'
673
+ assert ok?
674
+ assert_equal 'Hello World', body
675
+ end
676
+
677
+ it "passes to next route when condition calls pass explicitly" do
678
+ mock_app {
679
+ condition { pass unless params[:foo] == 'bar' }
680
+ get '/:foo' do
681
+ 'Hello World'
682
+ end
683
+ }
684
+
685
+ get '/bar'
686
+ assert ok?
687
+ assert_equal 'Hello World', body
688
+
689
+ get '/foo'
690
+ assert not_found?
691
+ end
692
+
693
+ it "passes to the next route when host_name does not match" do
694
+ mock_app {
695
+ host_name 'example.com'
696
+ get '/foo' do
697
+ 'Hello World'
698
+ end
699
+ }
700
+ get '/foo'
701
+ assert not_found?
702
+
703
+ get '/foo', {}, { 'HTTP_HOST' => 'example.com' }
704
+ assert_equal 200, status
705
+ assert_equal 'Hello World', body
706
+ end
707
+
708
+ it "passes to the next route when user_agent does not match" do
709
+ mock_app {
710
+ user_agent(/Foo/)
711
+ get '/foo' do
712
+ 'Hello World'
713
+ end
714
+ }
715
+ get '/foo'
716
+ assert not_found?
717
+
718
+ get '/foo', {}, { 'HTTP_USER_AGENT' => 'Foo Bar' }
719
+ assert_equal 200, status
720
+ assert_equal 'Hello World', body
721
+ end
722
+
723
+ it "treats missing user agent like an empty string" do
724
+ mock_app do
725
+ user_agent(/.*/)
726
+ get '/' do
727
+ "Hello World"
728
+ end
729
+ end
730
+ get '/'
731
+ assert_equal 200, status
732
+ assert_equal 'Hello World', body
733
+ end
734
+
735
+ it "makes captures in user agent pattern available in params[:agent]" do
736
+ mock_app {
737
+ user_agent(/Foo (.*)/)
738
+ get '/foo' do
739
+ 'Hello ' + params[:agent].first
740
+ end
741
+ }
742
+ get '/foo', {}, { 'HTTP_USER_AGENT' => 'Foo Bar' }
743
+ assert_equal 200, status
744
+ assert_equal 'Hello Bar', body
745
+ end
746
+
747
+ it "filters by accept header" do
748
+ mock_app {
749
+ get '/', :provides => :xml do
750
+ env['HTTP_ACCEPT']
751
+ end
752
+ get '/foo', :provides => :html do
753
+ env['HTTP_ACCEPT']
754
+ end
755
+ }
756
+
757
+ get '/', {}, { 'HTTP_ACCEPT' => 'application/xml' }
758
+ assert ok?
759
+ assert_equal 'application/xml', body
760
+ assert_equal 'application/xml;charset=utf-8', response.headers['Content-Type']
761
+
762
+ get '/', {}, { :accept => 'text/html' }
763
+ assert !ok?
764
+
765
+ get '/foo', {}, { 'HTTP_ACCEPT' => 'text/html;q=0.9' }
766
+ assert ok?
767
+ assert_equal 'text/html;q=0.9', body
768
+
769
+ get '/foo', {}, { 'HTTP_ACCEPT' => '' }
770
+ assert !ok?
771
+ end
772
+
773
+ it "filters by current Content-Type" do
774
+ mock_app do
775
+ before('/txt') { content_type :txt }
776
+ get('*', :provides => :txt) { 'txt' }
777
+
778
+ before('/html') { content_type :html }
779
+ get('*', :provides => :html) { 'html' }
780
+ end
781
+
782
+ get '/', {}, { 'HTTP_ACCEPT' => '*' }
783
+ assert ok?
784
+ assert_equal 'text/plain;charset=utf-8', response.headers['Content-Type']
785
+ assert_body 'txt'
786
+
787
+ get '/txt', {}, { 'HTTP_ACCEPT' => 'text/plain' }
788
+ assert ok?
789
+ assert_equal 'text/plain;charset=utf-8', response.headers['Content-Type']
790
+ assert_body 'txt'
791
+
792
+ get '/', {}, { 'HTTP_ACCEPT' => 'text/html' }
793
+ assert ok?
794
+ assert_equal 'text/html;charset=utf-8', response.headers['Content-Type']
795
+ assert_body 'html'
796
+ end
797
+
798
+ it "allows multiple mime types for accept header" do
799
+ types = ['image/jpeg', 'image/pjpeg']
800
+
801
+ mock_app {
802
+ get '/', :provides => types do
803
+ env['HTTP_ACCEPT']
804
+ end
805
+ }
806
+
807
+ types.each do |type|
808
+ get '/', {}, { 'HTTP_ACCEPT' => type }
809
+ assert ok?
810
+ assert_equal type, body
811
+ assert_equal type, response.headers['Content-Type']
812
+ end
813
+ end
814
+
815
+ it 'degrades gracefully when optional accept header is not provided' do
816
+ mock_app {
817
+ get '/', :provides => :xml do
818
+ env['HTTP_ACCEPT']
819
+ end
820
+ get '/' do
821
+ 'default'
822
+ end
823
+ }
824
+ get '/'
825
+ assert ok?
826
+ assert_equal 'default', body
827
+ end
828
+
829
+ it 'respects user agent prefferences for the content type' do
830
+ mock_app { get('/', :provides => [:png, :html]) { content_type }}
831
+ get '/', {}, { 'HTTP_ACCEPT' => 'image/png;q=0.5,text/html;q=0.8' }
832
+ assert_body 'text/html;charset=utf-8'
833
+ get '/', {}, { 'HTTP_ACCEPT' => 'image/png;q=0.8,text/html;q=0.5' }
834
+ assert_body 'image/png'
835
+ end
836
+
837
+ it 'accepts generic types' do
838
+ mock_app do
839
+ get('/', :provides => :xml) { content_type }
840
+ get('/') { 'no match' }
841
+ end
842
+ get '/'
843
+ assert_body 'no match'
844
+ get '/', {}, { 'HTTP_ACCEPT' => 'foo/*' }
845
+ assert_body 'no match'
846
+ get '/', {}, { 'HTTP_ACCEPT' => 'application/*' }
847
+ assert_body 'application/xml;charset=utf-8'
848
+ get '/', {}, { 'HTTP_ACCEPT' => '*/*' }
849
+ assert_body 'application/xml;charset=utf-8'
850
+ end
851
+
852
+ it 'prefers concrete over partly generic types' do
853
+ mock_app { get('/', :provides => [:png, :html]) { content_type }}
854
+ get '/', {}, { 'HTTP_ACCEPT' => 'image/*, text/html' }
855
+ assert_body 'text/html;charset=utf-8'
856
+ get '/', {}, { 'HTTP_ACCEPT' => 'image/png, text/*' }
857
+ assert_body 'image/png'
858
+ end
859
+
860
+ it 'prefers concrete over fully generic types' do
861
+ mock_app { get('/', :provides => [:png, :html]) { content_type }}
862
+ get '/', {}, { 'HTTP_ACCEPT' => '*/*, text/html' }
863
+ assert_body 'text/html;charset=utf-8'
864
+ get '/', {}, { 'HTTP_ACCEPT' => 'image/png, */*' }
865
+ assert_body 'image/png'
866
+ end
867
+
868
+ it 'prefers partly generic over fully generic types' do
869
+ mock_app { get('/', :provides => [:png, :html]) { content_type }}
870
+ get '/', {}, { 'HTTP_ACCEPT' => '*/*, text/*' }
871
+ assert_body 'text/html;charset=utf-8'
872
+ get '/', {}, { 'HTTP_ACCEPT' => 'image/*, */*' }
873
+ assert_body 'image/png'
874
+ end
875
+
876
+ it 'respects quality with generic types' do
877
+ mock_app { get('/', :provides => [:png, :html]) { content_type }}
878
+ get '/', {}, { 'HTTP_ACCEPT' => 'image/*;q=1, text/html;q=0' }
879
+ assert_body 'image/png'
880
+ get '/', {}, { 'HTTP_ACCEPT' => 'image/png;q=0.5, text/*;q=0.7' }
881
+ assert_body 'text/html;charset=utf-8'
882
+ end
883
+
884
+ it 'accepts both text/javascript and application/javascript for js' do
885
+ mock_app { get('/', :provides => :js) { content_type }}
886
+ get '/', {}, { 'HTTP_ACCEPT' => 'application/javascript' }
887
+ assert_body 'application/javascript;charset=utf-8'
888
+ get '/', {}, { 'HTTP_ACCEPT' => 'text/javascript' }
889
+ assert_body 'text/javascript;charset=utf-8'
890
+ end
891
+
892
+ it 'accepts both text/xml and application/xml for xml' do
893
+ mock_app { get('/', :provides => :xml) { content_type }}
894
+ get '/', {}, { 'HTTP_ACCEPT' => 'application/xml' }
895
+ assert_body 'application/xml;charset=utf-8'
896
+ get '/', {}, { 'HTTP_ACCEPT' => 'text/xml' }
897
+ assert_body 'text/xml;charset=utf-8'
898
+ end
899
+
900
+ it 'passes a single url param as block parameters when one param is specified' do
901
+ mock_app {
902
+ get '/:foo' do |foo|
903
+ assert_equal 'bar', foo
904
+ end
905
+ }
906
+
907
+ get '/bar'
908
+ assert ok?
909
+ end
910
+
911
+ it 'passes multiple params as block parameters when many are specified' do
912
+ mock_app {
913
+ get '/:foo/:bar/:baz' do |foo, bar, baz|
914
+ assert_equal 'abc', foo
915
+ assert_equal 'def', bar
916
+ assert_equal 'ghi', baz
917
+ end
918
+ }
919
+
920
+ get '/abc/def/ghi'
921
+ assert ok?
922
+ end
923
+
924
+ it 'passes regular expression captures as block parameters' do
925
+ mock_app {
926
+ get(/^\/fo(.*)\/ba(.*)/) do |foo, bar|
927
+ assert_equal 'orooomma', foo
928
+ assert_equal 'f', bar
929
+ 'looks good'
930
+ end
931
+ }
932
+
933
+ get '/foorooomma/baf'
934
+ assert ok?
935
+ assert_equal 'looks good', body
936
+ end
937
+
938
+ it "supports mixing multiple splat params like /*/foo/*/* as block parameters" do
939
+ mock_app {
940
+ get '/*/foo/*/*' do |foo, bar, baz|
941
+ assert_equal 'bar', foo
942
+ assert_equal 'bling', bar
943
+ assert_equal 'baz/boom', baz
944
+ 'looks good'
945
+ end
946
+ }
947
+
948
+ get '/bar/foo/bling/baz/boom'
949
+ assert ok?
950
+ assert_equal 'looks good', body
951
+ end
952
+
953
+ it 'raises an ArgumentError with block arity > 1 and too many values' do
954
+ mock_app do
955
+ get '/:foo/:bar/:baz' do |foo, bar|
956
+ 'quux'
957
+ end
958
+ end
959
+
960
+ assert_raise(ArgumentError) { get '/a/b/c' }
961
+ end
962
+
963
+ it 'raises an ArgumentError with block param arity > 1 and too few values' do
964
+ mock_app {
965
+ get '/:foo/:bar' do |foo, bar, baz|
966
+ 'quux'
967
+ end
968
+ }
969
+
970
+ assert_raise(ArgumentError) { get '/a/b' }
971
+ end
972
+
973
+ it 'succeeds if no block parameters are specified' do
974
+ mock_app {
975
+ get '/:foo/:bar' do
976
+ 'quux'
977
+ end
978
+ }
979
+
980
+ get '/a/b'
981
+ assert ok?
982
+ assert_equal 'quux', body
983
+ end
984
+
985
+ it 'passes all params with block param arity -1 (splat args)' do
986
+ mock_app {
987
+ get '/:foo/:bar' do |*args|
988
+ args.join
989
+ end
990
+ }
991
+
992
+ get '/a/b'
993
+ assert ok?
994
+ assert_equal 'ab', body
995
+ end
996
+
997
+ it 'allows custom route-conditions to be set via route options' do
998
+ protector = Module.new {
999
+ def protect(*args)
1000
+ condition {
1001
+ unless authorize(params["user"], params["password"])
1002
+ halt 403, "go away"
1003
+ end
1004
+ }
1005
+ end
1006
+ }
1007
+
1008
+ mock_app {
1009
+ register protector
1010
+
1011
+ helpers do
1012
+ def authorize(username, password)
1013
+ username == "foo" && password == "bar"
1014
+ end
1015
+ end
1016
+
1017
+ get "/", :protect => true do
1018
+ "hey"
1019
+ end
1020
+ }
1021
+
1022
+ get "/"
1023
+ assert forbidden?
1024
+ assert_equal "go away", body
1025
+
1026
+ get "/", :user => "foo", :password => "bar"
1027
+ assert ok?
1028
+ assert_equal "hey", body
1029
+ end
1030
+
1031
+ # NOTE Block params behaves differently under 1.8 and 1.9. Under 1.8, block
1032
+ # param arity is lax: declaring a mismatched number of block params results
1033
+ # in a warning. Under 1.9, block param arity is strict: mismatched block
1034
+ # arity raises an ArgumentError.
1035
+
1036
+ if RUBY_VERSION >= '1.9'
1037
+
1038
+ it 'raises an ArgumentError with block param arity 1 and no values' do
1039
+ mock_app {
1040
+ get '/foo' do |foo|
1041
+ 'quux'
1042
+ end
1043
+ }
1044
+
1045
+ assert_raise(ArgumentError) { get '/foo' }
1046
+ end
1047
+
1048
+ it 'raises an ArgumentError with block param arity 1 and too many values' do
1049
+ mock_app {
1050
+ get '/:foo/:bar/:baz' do |foo|
1051
+ 'quux'
1052
+ end
1053
+ }
1054
+
1055
+ assert_raise(ArgumentError) { get '/a/b/c' }
1056
+ end
1057
+
1058
+ else
1059
+
1060
+ it 'does not raise an ArgumentError with block param arity 1 and no values' do
1061
+ mock_app {
1062
+ get '/foo' do |foo|
1063
+ 'quux'
1064
+ end
1065
+ }
1066
+
1067
+ silence_warnings { get '/foo' }
1068
+ assert ok?
1069
+ assert_equal 'quux', body
1070
+ end
1071
+
1072
+ it 'does not raise an ArgumentError with block param arity 1 and too many values' do
1073
+ mock_app {
1074
+ get '/:foo/:bar/:baz' do |foo|
1075
+ 'quux'
1076
+ end
1077
+ }
1078
+
1079
+ silence_warnings { get '/a/b/c' }
1080
+ assert ok?
1081
+ assert_equal 'quux', body
1082
+ end
1083
+
1084
+ end
1085
+
1086
+ it "matches routes defined in superclasses" do
1087
+ base = Class.new(Sinatra::Base)
1088
+ base.get('/foo') { 'foo in baseclass' }
1089
+
1090
+ mock_app(base) {
1091
+ get('/bar') { 'bar in subclass' }
1092
+ }
1093
+
1094
+ get '/foo'
1095
+ assert ok?
1096
+ assert_equal 'foo in baseclass', body
1097
+
1098
+ get '/bar'
1099
+ assert ok?
1100
+ assert_equal 'bar in subclass', body
1101
+ end
1102
+
1103
+ it "matches routes in subclasses before superclasses" do
1104
+ base = Class.new(Sinatra::Base)
1105
+ base.get('/foo') { 'foo in baseclass' }
1106
+ base.get('/bar') { 'bar in baseclass' }
1107
+
1108
+ mock_app(base) {
1109
+ get('/foo') { 'foo in subclass' }
1110
+ }
1111
+
1112
+ get '/foo'
1113
+ assert ok?
1114
+ assert_equal 'foo in subclass', body
1115
+
1116
+ get '/bar'
1117
+ assert ok?
1118
+ assert_equal 'bar in baseclass', body
1119
+ end
1120
+
1121
+ it "adds hostname condition when it is in options" do
1122
+ mock_app {
1123
+ get '/foo', :host => 'host' do
1124
+ 'foo'
1125
+ end
1126
+ }
1127
+
1128
+ get '/foo'
1129
+ assert not_found?
1130
+ end
1131
+
1132
+ it 'allows using call to fire another request internally' do
1133
+ mock_app do
1134
+ get '/foo' do
1135
+ status, headers, body = call env.merge("PATH_INFO" => '/bar')
1136
+ [status, headers, body.each.map(&:upcase)]
1137
+ end
1138
+
1139
+ get '/bar' do
1140
+ "bar"
1141
+ end
1142
+ end
1143
+
1144
+ get '/foo'
1145
+ assert ok?
1146
+ assert_body "BAR"
1147
+ end
1148
+
1149
+ it 'plays well with other routing middleware' do
1150
+ middleware = Sinatra.new
1151
+ inner_app = Sinatra.new { get('/foo') { 'hello' } }
1152
+ builder = Rack::Builder.new do
1153
+ use middleware
1154
+ map('/test') { run inner_app }
1155
+ end
1156
+
1157
+ @app = builder.to_app
1158
+ get '/test/foo'
1159
+ assert ok?
1160
+ assert_body 'hello'
1161
+ end
1162
+
1163
+ it 'returns the route signature' do
1164
+ signature = list = nil
1165
+
1166
+ mock_app do
1167
+ signature = post('/') { }
1168
+ list = routes['POST']
1169
+ end
1170
+
1171
+ assert_equal Array, signature.class
1172
+ assert_equal 4, signature.length
1173
+ assert list.include?(signature)
1174
+ end
1175
+ end