sinatra-acd 1.4.5

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