sinatra-sinatra 0.8.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. data/AUTHORS +40 -0
  2. data/CHANGES +174 -0
  3. data/LICENSE +22 -0
  4. data/README.rdoc +545 -0
  5. data/Rakefile +180 -0
  6. data/compat/app_test.rb +300 -0
  7. data/compat/application_test.rb +334 -0
  8. data/compat/builder_test.rb +101 -0
  9. data/compat/custom_error_test.rb +62 -0
  10. data/compat/erb_test.rb +136 -0
  11. data/compat/events_test.rb +78 -0
  12. data/compat/filter_test.rb +30 -0
  13. data/compat/haml_test.rb +233 -0
  14. data/compat/helper.rb +30 -0
  15. data/compat/mapped_error_test.rb +72 -0
  16. data/compat/pipeline_test.rb +71 -0
  17. data/compat/public/foo.xml +1 -0
  18. data/compat/sass_test.rb +57 -0
  19. data/compat/sessions_test.rb +39 -0
  20. data/compat/streaming_test.rb +121 -0
  21. data/compat/sym_params_test.rb +19 -0
  22. data/compat/template_test.rb +30 -0
  23. data/compat/use_in_file_templates_test.rb +47 -0
  24. data/compat/views/foo.builder +1 -0
  25. data/compat/views/foo.erb +1 -0
  26. data/compat/views/foo.haml +1 -0
  27. data/compat/views/foo.sass +2 -0
  28. data/compat/views/foo_layout.erb +2 -0
  29. data/compat/views/foo_layout.haml +2 -0
  30. data/compat/views/layout_test/foo.builder +1 -0
  31. data/compat/views/layout_test/foo.erb +1 -0
  32. data/compat/views/layout_test/foo.haml +1 -0
  33. data/compat/views/layout_test/foo.sass +2 -0
  34. data/compat/views/layout_test/layout.builder +3 -0
  35. data/compat/views/layout_test/layout.erb +1 -0
  36. data/compat/views/layout_test/layout.haml +1 -0
  37. data/compat/views/layout_test/layout.sass +2 -0
  38. data/compat/views/no_layout/no_layout.builder +1 -0
  39. data/compat/views/no_layout/no_layout.haml +1 -0
  40. data/lib/sinatra/base.rb +818 -0
  41. data/lib/sinatra/compat.rb +239 -0
  42. data/lib/sinatra/images/404.png +0 -0
  43. data/lib/sinatra/images/500.png +0 -0
  44. data/lib/sinatra/main.rb +48 -0
  45. data/lib/sinatra/test/bacon.rb +17 -0
  46. data/lib/sinatra/test/rspec.rb +9 -0
  47. data/lib/sinatra/test/spec.rb +9 -0
  48. data/lib/sinatra/test/unit.rb +11 -0
  49. data/lib/sinatra/test.rb +109 -0
  50. data/lib/sinatra.rb +4 -0
  51. data/sinatra.gemspec +109 -0
  52. data/test/base_test.rb +68 -0
  53. data/test/builder_test.rb +64 -0
  54. data/test/data/reload_app_file.rb +3 -0
  55. data/test/erb_test.rb +50 -0
  56. data/test/filter_test.rb +35 -0
  57. data/test/haml_test.rb +68 -0
  58. data/test/helper.rb +20 -0
  59. data/test/helpers_test.rb +361 -0
  60. data/test/mapped_error_test.rb +160 -0
  61. data/test/middleware_test.rb +58 -0
  62. data/test/options_test.rb +97 -0
  63. data/test/reload_test.rb +61 -0
  64. data/test/request_test.rb +9 -0
  65. data/test/result_test.rb +88 -0
  66. data/test/routing_test.rb +334 -0
  67. data/test/sass_test.rb +36 -0
  68. data/test/sinatra_test.rb +13 -0
  69. data/test/static_test.rb +57 -0
  70. data/test/templates_test.rb +88 -0
  71. data/test/views/hello.builder +1 -0
  72. data/test/views/hello.erb +1 -0
  73. data/test/views/hello.haml +1 -0
  74. data/test/views/hello.sass +2 -0
  75. data/test/views/hello.test +1 -0
  76. data/test/views/layout2.builder +3 -0
  77. data/test/views/layout2.erb +2 -0
  78. data/test/views/layout2.haml +2 -0
  79. data/test/views/layout2.test +1 -0
  80. metadata +161 -0
@@ -0,0 +1,3 @@
1
+ $reload_count += 1
2
+
3
+ $reload_app.get('/') { 'Hello from reload file' }
data/test/erb_test.rb ADDED
@@ -0,0 +1,50 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ describe "ERB Templates" do
4
+ def erb_app(&block)
5
+ mock_app {
6
+ set :views, File.dirname(__FILE__) + '/views'
7
+ get '/', &block
8
+ }
9
+ get '/'
10
+ end
11
+
12
+ it 'renders inline ERB strings' do
13
+ erb_app { erb '<%= 1 + 1 %>' }
14
+ assert ok?
15
+ assert_equal '2', body
16
+ end
17
+
18
+ it 'renders .erb files in views path' do
19
+ erb_app { erb :hello }
20
+ assert ok?
21
+ assert_equal "Hello World\n", body
22
+ end
23
+
24
+ it 'takes a :locals option' do
25
+ erb_app {
26
+ locals = {:foo => 'Bar'}
27
+ erb '<%= foo %>', :locals => locals
28
+ }
29
+ assert ok?
30
+ assert_equal 'Bar', body
31
+ end
32
+
33
+ it "renders with inline layouts" do
34
+ mock_app {
35
+ layout { 'THIS. IS. <%= yield.upcase %>!' }
36
+ get('/') { erb 'Sparta' }
37
+ }
38
+ get '/'
39
+ assert ok?
40
+ assert_equal 'THIS. IS. SPARTA!', body
41
+ end
42
+
43
+ it "renders with file layouts" do
44
+ erb_app {
45
+ erb 'Hello World', :layout => :layout2
46
+ }
47
+ assert ok?
48
+ assert_equal "ERB Layout!\nHello World\n", body
49
+ end
50
+ end
@@ -0,0 +1,35 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ describe "Filters" do
4
+ it "executes filters in the order defined" do
5
+ count = 0
6
+ mock_app do
7
+ get('/') { 'Hello World' }
8
+ before {
9
+ fail 'count != 0' if count != 0
10
+ count = 1
11
+ }
12
+ before {
13
+ fail 'count != 1' if count != 1
14
+ count = 2
15
+ }
16
+ end
17
+
18
+ get '/'
19
+ assert ok?
20
+ assert_equal 2, count
21
+ assert_equal 'Hello World', body
22
+ end
23
+
24
+ it "allows filters to modify the request" do
25
+ mock_app {
26
+ get('/foo') { 'foo' }
27
+ get('/bar') { 'bar' }
28
+ before { request.path_info = '/bar' }
29
+ }
30
+
31
+ get '/foo'
32
+ assert ok?
33
+ assert_equal 'bar', body
34
+ end
35
+ end
data/test/haml_test.rb ADDED
@@ -0,0 +1,68 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ describe "HAML Templates" do
4
+ def haml_app(&block)
5
+ mock_app {
6
+ set :views, File.dirname(__FILE__) + '/views'
7
+ get '/', &block
8
+ }
9
+ get '/'
10
+ end
11
+
12
+ it 'renders inline HAML strings' do
13
+ haml_app { haml '%h1 Hiya' }
14
+ assert ok?
15
+ assert_equal "<h1>Hiya</h1>\n", body
16
+ end
17
+
18
+ it 'renders .haml files in views path' do
19
+ haml_app { haml :hello }
20
+ assert ok?
21
+ assert_equal "<h1>Hello From Haml</h1>\n", body
22
+ end
23
+
24
+ it "renders with inline layouts" do
25
+ mock_app {
26
+ layout { %q(%h1= 'THIS. IS. ' + yield.upcase) }
27
+ get('/') { haml '%em Sparta' }
28
+ }
29
+ get '/'
30
+ assert ok?
31
+ assert_equal "<h1>THIS. IS. <EM>SPARTA</EM></h1>\n", body
32
+ end
33
+
34
+ it "renders with file layouts" do
35
+ haml_app {
36
+ haml 'Hello World', :layout => :layout2
37
+ }
38
+ assert ok?
39
+ assert_equal "<h1>HAML Layout!</h1>\n<p>Hello World</p>\n", body
40
+ end
41
+
42
+ it "raises error if template not found" do
43
+ mock_app {
44
+ get('/') { haml :no_such_template }
45
+ }
46
+ assert_raise(Errno::ENOENT) { get('/') }
47
+ end
48
+
49
+ it "passes HAML options to the Haml engine" do
50
+ haml_app {
51
+ haml "!!!\n%h1 Hello World", :options => {:format => :html5}
52
+ }
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 {
59
+ set :haml, {:format => :html5}
60
+ get '/' do
61
+ haml "!!!\n%h1 Hello World"
62
+ end
63
+ }
64
+ get '/'
65
+ assert ok?
66
+ assert_equal "<!DOCTYPE html>\n<h1>Hello World</h1>\n", body
67
+ end
68
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,20 @@
1
+ begin
2
+ require 'test/spec'
3
+ rescue LoadError
4
+ require 'rubygems'
5
+ require 'test/spec'
6
+ end
7
+
8
+ $:.unshift File.dirname(File.dirname(__FILE__)) + '/lib'
9
+ require 'sinatra/base'
10
+ require 'sinatra/test'
11
+ require 'sinatra/test/spec'
12
+
13
+ module Sinatra::Test
14
+ # Sets up a Sinatra::Base subclass defined with the block
15
+ # given. Used in setup or individual spec methods to establish
16
+ # the application.
17
+ def mock_app(base=Sinatra::Base, &block)
18
+ @app = Sinatra.new(base, &block)
19
+ end
20
+ end
@@ -0,0 +1,361 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ describe 'Sinatra::Helpers' do
4
+ describe '#status' do
5
+ setup do
6
+ mock_app {
7
+ get '/' do
8
+ status 207
9
+ nil
10
+ end
11
+ }
12
+ end
13
+
14
+ it 'sets the response status code' do
15
+ get '/'
16
+ assert_equal 207, response.status
17
+ end
18
+ end
19
+
20
+ describe '#body' do
21
+ it 'takes a block for defered body generation' do
22
+ mock_app {
23
+ get '/' do
24
+ body { 'Hello World' }
25
+ end
26
+ }
27
+
28
+ get '/'
29
+ assert_equal 'Hello World', body
30
+ end
31
+
32
+ it 'takes a String, Array, or other object responding to #each' do
33
+ mock_app {
34
+ get '/' do
35
+ body 'Hello World'
36
+ end
37
+ }
38
+
39
+ get '/'
40
+ assert_equal 'Hello World', body
41
+ end
42
+ end
43
+
44
+ describe '#redirect' do
45
+ it 'uses a 302 when only a path is given' do
46
+ mock_app {
47
+ get '/' do
48
+ redirect '/foo'
49
+ fail 'redirect should halt'
50
+ end
51
+ }
52
+
53
+ get '/'
54
+ assert_equal 302, status
55
+ assert_equal '', body
56
+ assert_equal '/foo', response['Location']
57
+ end
58
+
59
+ it 'uses the code given when specified' do
60
+ mock_app {
61
+ get '/' do
62
+ redirect '/foo', 301
63
+ fail 'redirect should halt'
64
+ end
65
+ }
66
+
67
+ get '/'
68
+ assert_equal 301, status
69
+ assert_equal '', body
70
+ assert_equal '/foo', response['Location']
71
+ end
72
+ end
73
+
74
+ describe '#error' do
75
+ it 'sets a status code and halts' do
76
+ mock_app {
77
+ get '/' do
78
+ error 501
79
+ fail 'error should halt'
80
+ end
81
+ }
82
+
83
+ get '/'
84
+ assert_equal 501, status
85
+ assert_equal '', body
86
+ end
87
+
88
+ it 'takes an optional body' do
89
+ mock_app {
90
+ get '/' do
91
+ error 501, 'FAIL'
92
+ fail 'error should halt'
93
+ end
94
+ }
95
+
96
+ get '/'
97
+ assert_equal 501, status
98
+ assert_equal 'FAIL', body
99
+ end
100
+
101
+ it 'uses a 500 status code when first argument is a body' do
102
+ mock_app {
103
+ get '/' do
104
+ error 'FAIL'
105
+ fail 'error should halt'
106
+ end
107
+ }
108
+
109
+ get '/'
110
+ assert_equal 500, status
111
+ assert_equal 'FAIL', body
112
+ end
113
+ end
114
+
115
+ describe '#not_found' do
116
+ it 'halts with a 404 status' do
117
+ mock_app {
118
+ get '/' do
119
+ not_found
120
+ fail 'not_found should halt'
121
+ end
122
+ }
123
+
124
+ get '/'
125
+ assert_equal 404, status
126
+ assert_equal '', body
127
+ end
128
+ end
129
+
130
+ describe '#session' do
131
+ it 'uses the existing rack.session' do
132
+ mock_app {
133
+ get '/' do
134
+ session[:foo]
135
+ end
136
+ }
137
+
138
+ get '/', :env => { 'rack.session' => { :foo => 'bar' } }
139
+ assert_equal 'bar', body
140
+ end
141
+
142
+ it 'creates a new session when none provided' do
143
+ mock_app {
144
+ get '/' do
145
+ fail "session != {}" unless session.empty?
146
+ session[:foo] = 'bar'
147
+ 'Hi'
148
+ end
149
+ }
150
+
151
+ get '/'
152
+ assert_equal 'Hi', body
153
+ end
154
+ end
155
+
156
+ describe '#media_type' do
157
+ include Sinatra::Helpers
158
+ it "looks up media types in Rack's MIME registry" do
159
+ Rack::Mime::MIME_TYPES['.foo'] = 'application/foo'
160
+ assert_equal 'application/foo', media_type('foo')
161
+ assert_equal 'application/foo', media_type('.foo')
162
+ assert_equal 'application/foo', media_type(:foo)
163
+ end
164
+ it 'returns nil when given nil' do
165
+ assert media_type(nil).nil?
166
+ end
167
+ it 'returns nil when media type not registered' do
168
+ assert media_type(:bizzle).nil?
169
+ end
170
+ it 'returns the argument when given a media type string' do
171
+ assert_equal 'text/plain', media_type('text/plain')
172
+ end
173
+ end
174
+
175
+ describe '#content_type' do
176
+ it 'sets the Content-Type header' do
177
+ mock_app {
178
+ get '/' do
179
+ content_type 'text/plain'
180
+ 'Hello World'
181
+ end
182
+ }
183
+
184
+ get '/'
185
+ assert_equal 'text/plain', response['Content-Type']
186
+ assert_equal 'Hello World', body
187
+ end
188
+
189
+ it 'takes media type parameters (like charset=)' do
190
+ mock_app {
191
+ get '/' do
192
+ content_type 'text/html', :charset => 'utf-8'
193
+ "<h1>Hello, World</h1>"
194
+ end
195
+ }
196
+
197
+ get '/'
198
+ assert ok?
199
+ assert_equal 'text/html;charset=utf-8', response['Content-Type']
200
+ assert_equal "<h1>Hello, World</h1>", body
201
+ end
202
+
203
+ it "looks up symbols in Rack's mime types dictionary" do
204
+ Rack::Mime::MIME_TYPES['.foo'] = 'application/foo'
205
+ mock_app {
206
+ get '/foo.xml' do
207
+ content_type :foo
208
+ "I AM FOO"
209
+ end
210
+ }
211
+
212
+ get '/foo.xml'
213
+ assert ok?
214
+ assert_equal 'application/foo', response['Content-Type']
215
+ assert_equal 'I AM FOO', body
216
+ end
217
+
218
+ it 'fails when no mime type is registered for the argument provided' do
219
+ mock_app {
220
+ get '/foo.xml' do
221
+ content_type :bizzle
222
+ "I AM FOO"
223
+ end
224
+ }
225
+ assert_raise(RuntimeError) { get '/foo.xml' }
226
+ end
227
+ end
228
+
229
+ describe '#send_file' do
230
+ before {
231
+ @file = File.dirname(__FILE__) + '/file.txt'
232
+ File.open(@file, 'wb') { |io| io.write('Hello World') }
233
+ }
234
+ after {
235
+ File.unlink @file
236
+ @file = nil
237
+ }
238
+
239
+ def send_file_app
240
+ path = @file
241
+ mock_app {
242
+ get '/file.txt' do
243
+ send_file path
244
+ end
245
+ }
246
+ end
247
+
248
+ it "sends the contents of the file" do
249
+ send_file_app
250
+ get '/file.txt'
251
+ assert ok?
252
+ assert_equal 'Hello World', body
253
+ end
254
+
255
+ it 'sets the Content-Type response header if a mime-type can be located' do
256
+ send_file_app
257
+ get '/file.txt'
258
+ assert_equal 'text/plain', response['Content-Type']
259
+ end
260
+
261
+ it 'sets the Content-Length response header' do
262
+ send_file_app
263
+ get '/file.txt'
264
+ assert_equal 'Hello World'.length.to_s, response['Content-Length']
265
+ end
266
+
267
+ it 'sets the Last-Modified response header' do
268
+ send_file_app
269
+ get '/file.txt'
270
+ assert_equal File.mtime(@file).httpdate, response['Last-Modified']
271
+ end
272
+
273
+ it "returns a 404 when not found" do
274
+ mock_app {
275
+ get '/' do
276
+ send_file 'this-file-does-not-exist.txt'
277
+ end
278
+ }
279
+ get '/'
280
+ assert not_found?
281
+ end
282
+ end
283
+
284
+ describe '#last_modified' do
285
+ before do
286
+ now = Time.now
287
+ mock_app {
288
+ get '/' do
289
+ body { 'Hello World' }
290
+ last_modified now
291
+ 'Boo!'
292
+ end
293
+ }
294
+ @now = now
295
+ end
296
+
297
+ it 'sets the Last-Modified header to a valid RFC 2616 date value' do
298
+ get '/'
299
+ assert_equal @now.httpdate, response['Last-Modified']
300
+ end
301
+
302
+ it 'returns a body when conditional get misses' do
303
+ get '/'
304
+ assert_equal 200, status
305
+ assert_equal 'Boo!', body
306
+ end
307
+
308
+ it 'halts when a conditional GET matches' do
309
+ get '/', :env => { 'HTTP_IF_MODIFIED_SINCE' => @now.httpdate }
310
+ assert_equal 304, status
311
+ assert_equal '', body
312
+ end
313
+ end
314
+
315
+ describe '#etag' do
316
+ before do
317
+ mock_app {
318
+ get '/' do
319
+ body { 'Hello World' }
320
+ etag 'FOO'
321
+ 'Boo!'
322
+ end
323
+ }
324
+ end
325
+
326
+ it 'sets the ETag header' do
327
+ get '/'
328
+ assert_equal '"FOO"', response['ETag']
329
+ end
330
+
331
+ it 'returns a body when conditional get misses' do
332
+ get '/'
333
+ assert_equal 200, status
334
+ assert_equal 'Boo!', body
335
+ end
336
+
337
+ it 'halts when a conditional GET matches' do
338
+ get '/', :env => { 'HTTP_IF_NONE_MATCH' => '"FOO"' }
339
+ assert_equal 304, status
340
+ assert_equal '', body
341
+ end
342
+
343
+ it 'should handle multiple ETag values in If-None-Match header' do
344
+ get '/', :env => { 'HTTP_IF_NONE_MATCH' => '"BAR", *' }
345
+ assert_equal 304, status
346
+ assert_equal '', body
347
+ end
348
+
349
+ it 'uses a weak etag with the :weak option' do
350
+ mock_app {
351
+ get '/' do
352
+ etag 'FOO', :weak
353
+ "that's weak, dude."
354
+ end
355
+ }
356
+ get '/'
357
+ assert_equal 'W/"FOO"', response['ETag']
358
+ end
359
+
360
+ end
361
+ end
@@ -0,0 +1,160 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ describe 'Exception Mappings' do
4
+ class FooError < RuntimeError
5
+ end
6
+
7
+ it 'invokes handlers registered with ::error when raised' do
8
+ mock_app {
9
+ set :raise_errors, false
10
+ error(FooError) { 'Foo!' }
11
+ get '/' do
12
+ raise FooError
13
+ end
14
+ }
15
+ get '/'
16
+ assert_equal 500, status
17
+ assert_equal 'Foo!', body
18
+ end
19
+
20
+ it 'uses the Exception handler if no matching handler found' do
21
+ mock_app {
22
+ set :raise_errors, false
23
+ error(Exception) { 'Exception!' }
24
+ get '/' do
25
+ raise FooError
26
+ end
27
+ }
28
+ get '/'
29
+ assert_equal 500, status
30
+ assert_equal 'Exception!', body
31
+ end
32
+
33
+ it "sets env['sinatra.error'] to the rescued exception" do
34
+ mock_app {
35
+ set :raise_errors, false
36
+ error(FooError) {
37
+ fail "!env.include?('sinatra.error')" if !env.include?('sinatra.error')
38
+ fail unless env['sinatra.error'].kind_of?(FooError)
39
+ 'looks good'
40
+ }
41
+ get '/' do
42
+ raise FooError
43
+ end
44
+ }
45
+ get '/'
46
+ assert_equal 'looks good', body
47
+ end
48
+
49
+ it 'dumps errors to rack.errors when dump_errors is enabled' do
50
+ mock_app {
51
+ set :raise_errors, false
52
+ set :dump_errors, true
53
+ get('/') { raise FooError, 'BOOM!' }
54
+ }
55
+
56
+ get '/'
57
+ assert_equal 500, status
58
+ assert @response.errors =~ /FooError - BOOM!:/
59
+ end
60
+
61
+ it "raises without calling the handler when the raise_errors options is set" do
62
+ mock_app {
63
+ set :raise_errors, true
64
+ error(FooError) { "she's not there." }
65
+ get '/' do
66
+ raise FooError
67
+ end
68
+ }
69
+ assert_raise(FooError) { get '/' }
70
+ end
71
+
72
+ it "never raises Sinatra::NotFound beyond the application" do
73
+ mock_app {
74
+ set :raise_errors, true
75
+ get '/' do
76
+ raise Sinatra::NotFound
77
+ end
78
+ }
79
+ assert_nothing_raised { get '/' }
80
+ assert_equal 404, status
81
+ end
82
+
83
+ class FooNotFound < Sinatra::NotFound
84
+ end
85
+
86
+ it "cascades for subclasses of Sinatra::NotFound" do
87
+ mock_app {
88
+ set :raise_errors, true
89
+ error(FooNotFound) { "foo! not found." }
90
+ get '/' do
91
+ raise FooNotFound
92
+ end
93
+ }
94
+ assert_nothing_raised { get '/' }
95
+ assert_equal 404, status
96
+ assert_equal 'foo! not found.', body
97
+ end
98
+
99
+ it 'has a not_found method for backwards compatibility' do
100
+ mock_app {
101
+ not_found do
102
+ "Lost, are we?"
103
+ end
104
+ }
105
+
106
+ get '/test'
107
+ assert_equal 404, status
108
+ assert_equal "Lost, are we?", body
109
+ end
110
+ end
111
+
112
+ describe 'Custom Error Pages' do
113
+ it 'allows numeric status code mappings to be registered with ::error' do
114
+ mock_app {
115
+ set :raise_errors, false
116
+ error(500) { 'Foo!' }
117
+ get '/' do
118
+ [500, {}, 'Internal Foo Error']
119
+ end
120
+ }
121
+ get '/'
122
+ assert_equal 500, status
123
+ assert_equal 'Foo!', body
124
+ end
125
+
126
+ it 'allows ranges of status code mappings to be registered with :error' do
127
+ mock_app {
128
+ set :raise_errors, false
129
+ error(500..550) { "Error: #{response.status}" }
130
+ get '/' do
131
+ [507, {}, 'A very special error']
132
+ end
133
+ }
134
+ get '/'
135
+ assert_equal 507, status
136
+ assert_equal 'Error: 507', body
137
+ end
138
+
139
+ class FooError < RuntimeError
140
+ end
141
+
142
+ it 'runs after exception mappings and overwrites body' do
143
+ mock_app {
144
+ set :raise_errors, false
145
+ error FooError do
146
+ response.status = 502
147
+ 'from exception mapping'
148
+ end
149
+ error(500) { 'from 500 handler' }
150
+ error(502) { 'from custom error page' }
151
+
152
+ get '/' do
153
+ raise FooError
154
+ end
155
+ }
156
+ get '/'
157
+ assert_equal 502, status
158
+ assert_equal 'from custom error page', body
159
+ end
160
+ end