sinatra-base 1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. data/AUTHORS +43 -0
  2. data/CHANGES +511 -0
  3. data/LICENSE +22 -0
  4. data/README.jp.rdoc +552 -0
  5. data/README.rdoc +636 -0
  6. data/Rakefile +116 -0
  7. data/lib/sinatra.rb +7 -0
  8. data/lib/sinatra/base.rb +1167 -0
  9. data/lib/sinatra/images/404.png +0 -0
  10. data/lib/sinatra/images/500.png +0 -0
  11. data/lib/sinatra/main.rb +28 -0
  12. data/lib/sinatra/showexceptions.rb +307 -0
  13. data/lib/sinatra/tilt.rb +746 -0
  14. data/sinatra-base.gemspec +94 -0
  15. data/test/base_test.rb +160 -0
  16. data/test/builder_test.rb +65 -0
  17. data/test/contest.rb +64 -0
  18. data/test/erb_test.rb +81 -0
  19. data/test/erubis_test.rb +82 -0
  20. data/test/extensions_test.rb +100 -0
  21. data/test/filter_test.rb +221 -0
  22. data/test/haml_test.rb +95 -0
  23. data/test/helper.rb +76 -0
  24. data/test/helpers_test.rb +582 -0
  25. data/test/less_test.rb +37 -0
  26. data/test/mapped_error_test.rb +197 -0
  27. data/test/middleware_test.rb +68 -0
  28. data/test/public/favicon.ico +0 -0
  29. data/test/request_test.rb +33 -0
  30. data/test/response_test.rb +42 -0
  31. data/test/result_test.rb +98 -0
  32. data/test/route_added_hook_test.rb +59 -0
  33. data/test/routing_test.rb +860 -0
  34. data/test/sass_test.rb +85 -0
  35. data/test/server_test.rb +47 -0
  36. data/test/settings_test.rb +368 -0
  37. data/test/sinatra_test.rb +13 -0
  38. data/test/static_test.rb +93 -0
  39. data/test/templates_test.rb +159 -0
  40. data/test/views/error.builder +3 -0
  41. data/test/views/error.erb +3 -0
  42. data/test/views/error.erubis +3 -0
  43. data/test/views/error.haml +3 -0
  44. data/test/views/error.sass +2 -0
  45. data/test/views/foo/hello.test +1 -0
  46. data/test/views/hello.builder +1 -0
  47. data/test/views/hello.erb +1 -0
  48. data/test/views/hello.erubis +1 -0
  49. data/test/views/hello.haml +1 -0
  50. data/test/views/hello.less +5 -0
  51. data/test/views/hello.sass +2 -0
  52. data/test/views/hello.test +1 -0
  53. data/test/views/layout2.builder +3 -0
  54. data/test/views/layout2.erb +2 -0
  55. data/test/views/layout2.erubis +2 -0
  56. data/test/views/layout2.haml +2 -0
  57. data/test/views/layout2.test +1 -0
  58. metadata +257 -0
@@ -0,0 +1,59 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ module RouteAddedTest
4
+ @routes, @procs = [], []
5
+ def self.routes ; @routes ; end
6
+ def self.procs ; @procs ; end
7
+ def self.route_added(verb, path, proc)
8
+ @routes << [verb, path]
9
+ @procs << proc
10
+ end
11
+ end
12
+
13
+ class RouteAddedHookTest < Test::Unit::TestCase
14
+ setup {
15
+ RouteAddedTest.routes.clear
16
+ RouteAddedTest.procs.clear
17
+ }
18
+
19
+ it "should be notified of an added route" do
20
+ mock_app(Class.new(Sinatra::Base)) {
21
+ register RouteAddedTest
22
+ get('/') {}
23
+ }
24
+
25
+ assert_equal [["GET", "/"], ["HEAD", "/"]],
26
+ RouteAddedTest.routes
27
+ end
28
+
29
+ it "should include hooks from superclass" do
30
+ a = Class.new(Class.new(Sinatra::Base))
31
+ b = Class.new(a)
32
+
33
+ a.register RouteAddedTest
34
+ b.class_eval { post("/sub_app_route") {} }
35
+
36
+ assert_equal [["POST", "/sub_app_route"]],
37
+ RouteAddedTest.routes
38
+ end
39
+
40
+ it "should only run once per extension" do
41
+ mock_app(Class.new(Sinatra::Base)) {
42
+ register RouteAddedTest
43
+ register RouteAddedTest
44
+ get('/') {}
45
+ }
46
+
47
+ assert_equal [["GET", "/"], ["HEAD", "/"]],
48
+ RouteAddedTest.routes
49
+ end
50
+
51
+ it "should pass route blocks as an argument" do
52
+ mock_app(Class.new(Sinatra::Base)) {
53
+ register RouteAddedTest
54
+ get('/') {}
55
+ }
56
+
57
+ assert_kind_of Proc, RouteAddedTest.procs.first
58
+ end
59
+ end
@@ -0,0 +1,860 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ # Helper method for easy route pattern matching testing
4
+ def route_def(pattern)
5
+ mock_app { get(pattern) { } }
6
+ end
7
+
8
+ class RegexpLookAlike
9
+ class MatchData
10
+ def captures
11
+ ["this", "is", "a", "test"]
12
+ end
13
+ end
14
+
15
+ def match(string)
16
+ ::RegexpLookAlike::MatchData.new if string == "/this/is/a/test/"
17
+ end
18
+
19
+ def keys
20
+ ["one", "two", "three", "four"]
21
+ end
22
+ end
23
+
24
+ class RoutingTest < Test::Unit::TestCase
25
+ %w[get put post delete].each do |verb|
26
+ it "defines #{verb.upcase} request handlers with #{verb}" do
27
+ mock_app {
28
+ send verb, '/hello' do
29
+ 'Hello World'
30
+ end
31
+ }
32
+
33
+ request = Rack::MockRequest.new(@app)
34
+ response = request.request(verb.upcase, '/hello', {})
35
+ assert response.ok?
36
+ assert_equal 'Hello World', response.body
37
+ end
38
+ end
39
+
40
+ it "defines HEAD request handlers with HEAD" do
41
+ mock_app {
42
+ head '/hello' do
43
+ response['X-Hello'] = 'World!'
44
+ 'remove me'
45
+ end
46
+ }
47
+
48
+ request = Rack::MockRequest.new(@app)
49
+ response = request.request('HEAD', '/hello', {})
50
+ assert response.ok?
51
+ assert_equal 'World!', response['X-Hello']
52
+ assert_equal '', response.body
53
+ end
54
+
55
+ it "404s when no route satisfies the request" do
56
+ mock_app {
57
+ get('/foo') { }
58
+ }
59
+ get '/bar'
60
+ assert_equal 404, status
61
+ end
62
+
63
+ it "404s and sets X-Cascade header when no route satisfies the request" do
64
+ mock_app {
65
+ get('/foo') { }
66
+ }
67
+ get '/bar'
68
+ assert_equal 404, status
69
+ assert_equal 'pass', response.headers['X-Cascade']
70
+ end
71
+
72
+ it "overrides the content-type in error handlers" do
73
+ mock_app {
74
+ before { content_type 'text/plain' }
75
+ error Sinatra::NotFound do
76
+ content_type "text/html"
77
+ "<h1>Not Found</h1>"
78
+ end
79
+ }
80
+
81
+ get '/foo'
82
+ assert_equal 404, status
83
+ assert_equal 'text/html', response["Content-Type"]
84
+ assert_equal "<h1>Not Found</h1>", response.body
85
+ end
86
+
87
+ it 'takes multiple definitions of a route' do
88
+ mock_app {
89
+ user_agent(/Foo/)
90
+ get '/foo' do
91
+ 'foo'
92
+ end
93
+
94
+ get '/foo' do
95
+ 'not foo'
96
+ end
97
+ }
98
+
99
+ get '/foo', {}, 'HTTP_USER_AGENT' => 'Foo'
100
+ assert ok?
101
+ assert_equal 'foo', body
102
+
103
+ get '/foo'
104
+ assert ok?
105
+ assert_equal 'not foo', body
106
+ end
107
+
108
+ it "exposes params with indifferent hash" do
109
+ mock_app {
110
+ get '/:foo' do
111
+ assert_equal 'bar', params['foo']
112
+ assert_equal 'bar', params[:foo]
113
+ 'well, alright'
114
+ end
115
+ }
116
+ get '/bar'
117
+ assert_equal 'well, alright', body
118
+ end
119
+
120
+ it "merges named params and query string params in params" do
121
+ mock_app {
122
+ get '/:foo' do
123
+ assert_equal 'bar', params['foo']
124
+ assert_equal 'biz', params['baz']
125
+ end
126
+ }
127
+ get '/bar?baz=biz'
128
+ assert ok?
129
+ end
130
+
131
+ it "supports named params like /hello/:person" do
132
+ mock_app {
133
+ get '/hello/:person' do
134
+ "Hello #{params['person']}"
135
+ end
136
+ }
137
+ get '/hello/Frank'
138
+ assert_equal 'Hello Frank', body
139
+ end
140
+
141
+ it "supports optional named params like /?:foo?/?:bar?" do
142
+ mock_app {
143
+ get '/?:foo?/?:bar?' do
144
+ "foo=#{params[:foo]};bar=#{params[:bar]}"
145
+ end
146
+ }
147
+
148
+ get '/hello/world'
149
+ assert ok?
150
+ assert_equal "foo=hello;bar=world", body
151
+
152
+ get '/hello'
153
+ assert ok?
154
+ assert_equal "foo=hello;bar=", body
155
+
156
+ get '/'
157
+ assert ok?
158
+ assert_equal "foo=;bar=", body
159
+ end
160
+
161
+ it "supports single splat params like /*" do
162
+ mock_app {
163
+ get '/*' do
164
+ assert params['splat'].kind_of?(Array)
165
+ params['splat'].join "\n"
166
+ end
167
+ }
168
+
169
+ get '/foo'
170
+ assert_equal "foo", body
171
+
172
+ get '/foo/bar/baz'
173
+ assert_equal "foo/bar/baz", body
174
+ end
175
+
176
+ it "supports mixing multiple splat params like /*/foo/*/*" do
177
+ mock_app {
178
+ get '/*/foo/*/*' do
179
+ assert params['splat'].kind_of?(Array)
180
+ params['splat'].join "\n"
181
+ end
182
+ }
183
+
184
+ get '/bar/foo/bling/baz/boom'
185
+ assert_equal "bar\nbling\nbaz/boom", body
186
+
187
+ get '/bar/foo/baz'
188
+ assert not_found?
189
+ end
190
+
191
+ it "supports mixing named and splat params like /:foo/*" do
192
+ mock_app {
193
+ get '/:foo/*' do
194
+ assert_equal 'foo', params['foo']
195
+ assert_equal ['bar/baz'], params['splat']
196
+ end
197
+ }
198
+
199
+ get '/foo/bar/baz'
200
+ assert ok?
201
+ end
202
+
203
+ it "matches a dot ('.') as part of a named param" do
204
+ mock_app {
205
+ get '/:foo/:bar' do
206
+ params[:foo]
207
+ end
208
+ }
209
+
210
+ get '/user@example.com/name'
211
+ assert_equal 200, response.status
212
+ assert_equal 'user@example.com', body
213
+ end
214
+
215
+ it "matches a literal dot ('.') outside of named params" do
216
+ mock_app {
217
+ get '/:file.:ext' do
218
+ assert_equal 'pony', params[:file]
219
+ assert_equal 'jpg', params[:ext]
220
+ 'right on'
221
+ end
222
+ }
223
+
224
+ get '/pony.jpg'
225
+ assert_equal 200, response.status
226
+ assert_equal 'right on', body
227
+ end
228
+
229
+ it "literally matches . in paths" do
230
+ route_def '/test.bar'
231
+
232
+ get '/test.bar'
233
+ assert ok?
234
+ get 'test0bar'
235
+ assert not_found?
236
+ end
237
+
238
+ it "literally matches $ in paths" do
239
+ route_def '/test$/'
240
+
241
+ get '/test$/'
242
+ assert ok?
243
+ end
244
+
245
+ it "literally matches + in paths" do
246
+ route_def '/te+st/'
247
+
248
+ get '/te%2Bst/'
249
+ assert ok?
250
+ get '/teeeeeeest/'
251
+ assert not_found?
252
+ end
253
+
254
+ it "literally matches () in paths" do
255
+ route_def '/test(bar)/'
256
+
257
+ get '/test(bar)/'
258
+ assert ok?
259
+ end
260
+
261
+ it "supports basic nested params" do
262
+ mock_app {
263
+ get '/hi' do
264
+ params["person"]["name"]
265
+ end
266
+ }
267
+
268
+ get "/hi?person[name]=John+Doe"
269
+ assert ok?
270
+ assert_equal "John Doe", body
271
+ end
272
+
273
+ it "exposes nested params with indifferent hash" do
274
+ mock_app {
275
+ get '/testme' do
276
+ assert_equal 'baz', params['bar']['foo']
277
+ assert_equal 'baz', params['bar'][:foo]
278
+ 'well, alright'
279
+ end
280
+ }
281
+ get '/testme?bar[foo]=baz'
282
+ assert_equal 'well, alright', body
283
+ end
284
+
285
+ it "supports deeply nested params" do
286
+ expected_params = {
287
+ "emacs" => {
288
+ "map" => { "goto-line" => "M-g g" },
289
+ "version" => "22.3.1"
290
+ },
291
+ "browser" => {
292
+ "firefox" => {"engine" => {"name"=>"spidermonkey", "version"=>"1.7.0"}},
293
+ "chrome" => {"engine" => {"name"=>"V8", "version"=>"1.0"}}
294
+ },
295
+ "paste" => {"name"=>"hello world", "syntax"=>"ruby"}
296
+ }
297
+ mock_app {
298
+ get '/foo' do
299
+ assert_equal expected_params, params
300
+ 'looks good'
301
+ end
302
+ }
303
+ get '/foo', expected_params
304
+ assert ok?
305
+ assert_equal 'looks good', body
306
+ end
307
+
308
+ it "preserves non-nested params" do
309
+ mock_app {
310
+ get '/foo' do
311
+ assert_equal "2", params["article_id"]
312
+ assert_equal "awesome", params['comment']['body']
313
+ assert_nil params['comment[body]']
314
+ 'looks good'
315
+ end
316
+ }
317
+
318
+ get '/foo?article_id=2&comment[body]=awesome'
319
+ assert ok?
320
+ assert_equal 'looks good', body
321
+ end
322
+
323
+ it "matches paths that include spaces encoded with %20" do
324
+ mock_app {
325
+ get '/path with spaces' do
326
+ 'looks good'
327
+ end
328
+ }
329
+
330
+ get '/path%20with%20spaces'
331
+ assert ok?
332
+ assert_equal 'looks good', body
333
+ end
334
+
335
+ it "matches paths that include spaces encoded with +" do
336
+ mock_app {
337
+ get '/path with spaces' do
338
+ 'looks good'
339
+ end
340
+ }
341
+
342
+ get '/path+with+spaces'
343
+ assert ok?
344
+ assert_equal 'looks good', body
345
+ end
346
+
347
+ it "URL decodes named parameters and splats" do
348
+ mock_app {
349
+ get '/:foo/*' do
350
+ assert_equal 'hello world', params['foo']
351
+ assert_equal ['how are you'], params['splat']
352
+ nil
353
+ end
354
+ }
355
+
356
+ get '/hello%20world/how%20are%20you'
357
+ assert ok?
358
+ end
359
+
360
+ it 'supports regular expressions' do
361
+ mock_app {
362
+ get(/^\/foo...\/bar$/) do
363
+ 'Hello World'
364
+ end
365
+ }
366
+
367
+ get '/foooom/bar'
368
+ assert ok?
369
+ assert_equal 'Hello World', body
370
+ end
371
+
372
+ it 'makes regular expression captures available in params[:captures]' do
373
+ mock_app {
374
+ get(/^\/fo(.*)\/ba(.*)/) do
375
+ assert_equal ['orooomma', 'f'], params[:captures]
376
+ 'right on'
377
+ end
378
+ }
379
+
380
+ get '/foorooomma/baf'
381
+ assert ok?
382
+ assert_equal 'right on', body
383
+ end
384
+
385
+ it 'supports regular expression look-alike routes' do
386
+ mock_app {
387
+ get(RegexpLookAlike.new) do
388
+ assert_equal 'this', params[:one]
389
+ assert_equal 'is', params[:two]
390
+ assert_equal 'a', params[:three]
391
+ assert_equal 'test', params[:four]
392
+ 'right on'
393
+ end
394
+ }
395
+
396
+ get '/this/is/a/test/'
397
+ assert ok?
398
+ assert_equal 'right on', body
399
+ end
400
+
401
+ it 'raises a TypeError when pattern is not a String or Regexp' do
402
+ assert_raise(TypeError) {
403
+ mock_app { get(42){} }
404
+ }
405
+ end
406
+
407
+ it "returns response immediately on halt" do
408
+ mock_app {
409
+ get '/' do
410
+ halt 'Hello World'
411
+ 'Boo-hoo World'
412
+ end
413
+ }
414
+
415
+ get '/'
416
+ assert ok?
417
+ assert_equal 'Hello World', body
418
+ end
419
+
420
+ it "halts with a response tuple" do
421
+ mock_app {
422
+ get '/' do
423
+ halt 295, {'Content-Type' => 'text/plain'}, 'Hello World'
424
+ end
425
+ }
426
+
427
+ get '/'
428
+ assert_equal 295, status
429
+ assert_equal 'text/plain', response['Content-Type']
430
+ assert_equal 'Hello World', body
431
+ end
432
+
433
+ it "halts with an array of strings" do
434
+ mock_app {
435
+ get '/' do
436
+ halt %w[Hello World How Are You]
437
+ end
438
+ }
439
+
440
+ get '/'
441
+ assert_equal 'HelloWorldHowAreYou', body
442
+ end
443
+
444
+ it "transitions to the next matching route on pass" do
445
+ mock_app {
446
+ get '/:foo' do
447
+ pass
448
+ 'Hello Foo'
449
+ end
450
+
451
+ get '/*' do
452
+ assert !params.include?('foo')
453
+ 'Hello World'
454
+ end
455
+ }
456
+
457
+ get '/bar'
458
+ assert ok?
459
+ assert_equal 'Hello World', body
460
+ end
461
+
462
+ it "transitions to 404 when passed and no subsequent route matches" do
463
+ mock_app {
464
+ get '/:foo' do
465
+ pass
466
+ 'Hello Foo'
467
+ end
468
+ }
469
+
470
+ get '/bar'
471
+ assert not_found?
472
+ end
473
+
474
+ it "transitions to 404 and sets X-Cascade header when passed and no subsequent route matches" do
475
+ mock_app {
476
+ get '/:foo' do
477
+ pass
478
+ 'Hello Foo'
479
+ end
480
+
481
+ get '/bar' do
482
+ 'Hello Bar'
483
+ end
484
+ }
485
+
486
+ get '/foo'
487
+ assert not_found?
488
+ assert_equal 'pass', response.headers['X-Cascade']
489
+ end
490
+
491
+ it "uses optional block passed to pass as route block if no other route is found" do
492
+ mock_app {
493
+ get "/" do
494
+ pass do
495
+ "this"
496
+ end
497
+ "not this"
498
+ end
499
+ }
500
+
501
+ get "/"
502
+ assert ok?
503
+ assert "this", body
504
+ end
505
+
506
+ it "passes when matching condition returns false" do
507
+ mock_app {
508
+ condition { params[:foo] == 'bar' }
509
+ get '/:foo' do
510
+ 'Hello World'
511
+ end
512
+ }
513
+
514
+ get '/bar'
515
+ assert ok?
516
+ assert_equal 'Hello World', body
517
+
518
+ get '/foo'
519
+ assert not_found?
520
+ end
521
+
522
+ it "does not pass when matching condition returns nil" do
523
+ mock_app {
524
+ condition { nil }
525
+ get '/:foo' do
526
+ 'Hello World'
527
+ end
528
+ }
529
+
530
+ get '/bar'
531
+ assert ok?
532
+ assert_equal 'Hello World', body
533
+ end
534
+
535
+ it "passes to next route when condition calls pass explicitly" do
536
+ mock_app {
537
+ condition { pass unless params[:foo] == 'bar' }
538
+ get '/:foo' do
539
+ 'Hello World'
540
+ end
541
+ }
542
+
543
+ get '/bar'
544
+ assert ok?
545
+ assert_equal 'Hello World', body
546
+
547
+ get '/foo'
548
+ assert not_found?
549
+ end
550
+
551
+ it "passes to the next route when host_name does not match" do
552
+ mock_app {
553
+ host_name 'example.com'
554
+ get '/foo' do
555
+ 'Hello World'
556
+ end
557
+ }
558
+ get '/foo'
559
+ assert not_found?
560
+
561
+ get '/foo', {}, { 'HTTP_HOST' => 'example.com' }
562
+ assert_equal 200, status
563
+ assert_equal 'Hello World', body
564
+ end
565
+
566
+ it "passes to the next route when user_agent does not match" do
567
+ mock_app {
568
+ user_agent(/Foo/)
569
+ get '/foo' do
570
+ 'Hello World'
571
+ end
572
+ }
573
+ get '/foo'
574
+ assert not_found?
575
+
576
+ get '/foo', {}, { 'HTTP_USER_AGENT' => 'Foo Bar' }
577
+ assert_equal 200, status
578
+ assert_equal 'Hello World', body
579
+ end
580
+
581
+ it "makes captures in user agent pattern available in params[:agent]" do
582
+ mock_app {
583
+ user_agent(/Foo (.*)/)
584
+ get '/foo' do
585
+ 'Hello ' + params[:agent].first
586
+ end
587
+ }
588
+ get '/foo', {}, { 'HTTP_USER_AGENT' => 'Foo Bar' }
589
+ assert_equal 200, status
590
+ assert_equal 'Hello Bar', body
591
+ end
592
+
593
+ it "filters by accept header" do
594
+ mock_app {
595
+ get '/', :provides => :xml do
596
+ request.env['HTTP_ACCEPT']
597
+ end
598
+ }
599
+
600
+ get '/', {}, { 'HTTP_ACCEPT' => 'application/xml' }
601
+ assert ok?
602
+ assert_equal 'application/xml', body
603
+ assert_equal 'application/xml', response.headers['Content-Type']
604
+
605
+ get '/', {}, { :accept => 'text/html' }
606
+ assert !ok?
607
+ end
608
+
609
+ it "allows multiple mime types for accept header" do
610
+ types = ['image/jpeg', 'image/pjpeg']
611
+
612
+ mock_app {
613
+ get '/', :provides => types do
614
+ request.env['HTTP_ACCEPT']
615
+ end
616
+ }
617
+
618
+ types.each do |type|
619
+ get '/', {}, { 'HTTP_ACCEPT' => type }
620
+ assert ok?
621
+ assert_equal type, body
622
+ assert_equal type, response.headers['Content-Type']
623
+ end
624
+ end
625
+
626
+ it 'degrades gracefully when optional accept header is not provided' do
627
+ mock_app {
628
+ get '/', :provides => :xml do
629
+ request.env['HTTP_ACCEPT']
630
+ end
631
+ get '/' do
632
+ 'default'
633
+ end
634
+ }
635
+ get '/'
636
+ assert ok?
637
+ assert_equal 'default', body
638
+ end
639
+
640
+ it 'passes a single url param as block parameters when one param is specified' do
641
+ mock_app {
642
+ get '/:foo' do |foo|
643
+ assert_equal 'bar', foo
644
+ end
645
+ }
646
+
647
+ get '/bar'
648
+ assert ok?
649
+ end
650
+
651
+ it 'passes multiple params as block parameters when many are specified' do
652
+ mock_app {
653
+ get '/:foo/:bar/:baz' do |foo, bar, baz|
654
+ assert_equal 'abc', foo
655
+ assert_equal 'def', bar
656
+ assert_equal 'ghi', baz
657
+ end
658
+ }
659
+
660
+ get '/abc/def/ghi'
661
+ assert ok?
662
+ end
663
+
664
+ it 'passes regular expression captures as block parameters' do
665
+ mock_app {
666
+ get(/^\/fo(.*)\/ba(.*)/) do |foo, bar|
667
+ assert_equal 'orooomma', foo
668
+ assert_equal 'f', bar
669
+ 'looks good'
670
+ end
671
+ }
672
+
673
+ get '/foorooomma/baf'
674
+ assert ok?
675
+ assert_equal 'looks good', body
676
+ end
677
+
678
+ it "supports mixing multiple splat params like /*/foo/*/* as block parameters" do
679
+ mock_app {
680
+ get '/*/foo/*/*' do |foo, bar, baz|
681
+ assert_equal 'bar', foo
682
+ assert_equal 'bling', bar
683
+ assert_equal 'baz/boom', baz
684
+ 'looks good'
685
+ end
686
+ }
687
+
688
+ get '/bar/foo/bling/baz/boom'
689
+ assert ok?
690
+ assert_equal 'looks good', body
691
+ end
692
+
693
+ it 'raises an ArgumentError with block arity > 1 and too many values' do
694
+ mock_app {
695
+ get '/:foo/:bar/:baz' do |foo, bar|
696
+ 'quux'
697
+ end
698
+ }
699
+
700
+ assert_raise(ArgumentError) { get '/a/b/c' }
701
+ end
702
+
703
+ it 'raises an ArgumentError with block param arity > 1 and too few values' do
704
+ mock_app {
705
+ get '/:foo/:bar' do |foo, bar, baz|
706
+ 'quux'
707
+ end
708
+ }
709
+
710
+ assert_raise(ArgumentError) { get '/a/b' }
711
+ end
712
+
713
+ it 'succeeds if no block parameters are specified' do
714
+ mock_app {
715
+ get '/:foo/:bar' do
716
+ 'quux'
717
+ end
718
+ }
719
+
720
+ get '/a/b'
721
+ assert ok?
722
+ assert_equal 'quux', body
723
+ end
724
+
725
+ it 'passes all params with block param arity -1 (splat args)' do
726
+ mock_app {
727
+ get '/:foo/:bar' do |*args|
728
+ args.join
729
+ end
730
+ }
731
+
732
+ get '/a/b'
733
+ assert ok?
734
+ assert_equal 'ab', body
735
+ end
736
+
737
+ it 'allows custom route-conditions to be set via route options' do
738
+ protector = Module.new {
739
+ def protect(*args)
740
+ condition {
741
+ unless authorize(params["user"], params["password"])
742
+ halt 403, "go away"
743
+ end
744
+ }
745
+ end
746
+ }
747
+
748
+ mock_app {
749
+ register protector
750
+
751
+ helpers do
752
+ def authorize(username, password)
753
+ username == "foo" && password == "bar"
754
+ end
755
+ end
756
+
757
+ get "/", :protect => true do
758
+ "hey"
759
+ end
760
+ }
761
+
762
+ get "/"
763
+ assert forbidden?
764
+ assert_equal "go away", body
765
+
766
+ get "/", :user => "foo", :password => "bar"
767
+ assert ok?
768
+ assert_equal "hey", body
769
+ end
770
+
771
+ # NOTE Block params behaves differently under 1.8 and 1.9. Under 1.8, block
772
+ # param arity is lax: declaring a mismatched number of block params results
773
+ # in a warning. Under 1.9, block param arity is strict: mismatched block
774
+ # arity raises an ArgumentError.
775
+
776
+ if RUBY_VERSION >= '1.9'
777
+
778
+ it 'raises an ArgumentError with block param arity 1 and no values' do
779
+ mock_app {
780
+ get '/foo' do |foo|
781
+ 'quux'
782
+ end
783
+ }
784
+
785
+ assert_raise(ArgumentError) { get '/foo' }
786
+ end
787
+
788
+ it 'raises an ArgumentError with block param arity 1 and too many values' do
789
+ mock_app {
790
+ get '/:foo/:bar/:baz' do |foo|
791
+ 'quux'
792
+ end
793
+ }
794
+
795
+ assert_raise(ArgumentError) { get '/a/b/c' }
796
+ end
797
+
798
+ else
799
+
800
+ it 'does not raise an ArgumentError with block param arity 1 and no values' do
801
+ mock_app {
802
+ get '/foo' do |foo|
803
+ 'quux'
804
+ end
805
+ }
806
+
807
+ silence_warnings { get '/foo' }
808
+ assert ok?
809
+ assert_equal 'quux', body
810
+ end
811
+
812
+ it 'does not raise an ArgumentError with block param arity 1 and too many values' do
813
+ mock_app {
814
+ get '/:foo/:bar/:baz' do |foo|
815
+ 'quux'
816
+ end
817
+ }
818
+
819
+ silence_warnings { get '/a/b/c' }
820
+ assert ok?
821
+ assert_equal 'quux', body
822
+ end
823
+
824
+ end
825
+
826
+ it "matches routes defined in superclasses" do
827
+ base = Class.new(Sinatra::Base)
828
+ base.get('/foo') { 'foo in baseclass' }
829
+
830
+ mock_app(base) {
831
+ get('/bar') { 'bar in subclass' }
832
+ }
833
+
834
+ get '/foo'
835
+ assert ok?
836
+ assert_equal 'foo in baseclass', body
837
+
838
+ get '/bar'
839
+ assert ok?
840
+ assert_equal 'bar in subclass', body
841
+ end
842
+
843
+ it "matches routes in subclasses before superclasses" do
844
+ base = Class.new(Sinatra::Base)
845
+ base.get('/foo') { 'foo in baseclass' }
846
+ base.get('/bar') { 'bar in baseclass' }
847
+
848
+ mock_app(base) {
849
+ get('/foo') { 'foo in subclass' }
850
+ }
851
+
852
+ get '/foo'
853
+ assert ok?
854
+ assert_equal 'foo in subclass', body
855
+
856
+ get '/bar'
857
+ assert ok?
858
+ assert_equal 'bar in baseclass', body
859
+ end
860
+ end