devcenter 0.0.1

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