scorched 0.22 → 0.23

Sign up to get free protection for your applications and to get access to all the features.
File without changes
File without changes
@@ -1,3 +1,3 @@
1
1
  module Scorched
2
- VERSION = '0.22'
3
- end
2
+ VERSION = '0.23'
3
+ end
@@ -15,9 +15,10 @@ Gem::Specification.new 'scorched', Scorched::VERSION do |s|
15
15
  s.required_ruby_version = '>= 2.0.0'
16
16
 
17
17
  s.add_dependency 'rack', '~> 1.4'
18
- s.add_dependency 'rack-accept', '~> 0.4'
18
+ s.add_dependency 'rack-accept', '~> 0.4' # Used for Accept-Charset, Accept-Encoding and Accept-Language headers.
19
+ s.add_dependency 'scorched-accept', '~> 0.1' # Used for Accept header.
19
20
  s.add_dependency 'tilt', '~> 1.4'
20
21
  s.add_development_dependency 'rack-test', '~> 0.6'
21
22
  s.add_development_dependency 'rspec', '~> 2.9'
22
- s.add_development_dependency 'rake', '>= 10'
23
- end
23
+ s.add_development_dependency 'rake', '~> 10.4'
24
+ end
@@ -5,30 +5,30 @@ module Scorched
5
5
  let(:generic_handler) do
6
6
  proc { |env| [200, {}, ['ok']] }
7
7
  end
8
-
8
+
9
9
  it "contains a default set of configuration options" do
10
10
  app.config.should be_a(Options)
11
11
  app.config.length.should > 0
12
12
  end
13
-
13
+
14
14
  it "contains a set of default conditions" do
15
15
  app.conditions.should be_a(Options)
16
16
  app.conditions.length.should > 0
17
17
  app.conditions[:method].should be_a(Proc)
18
18
  end
19
-
19
+
20
20
  describe "basic route handling" do
21
21
  it "gracefully handles 404 errors" do
22
22
  response = rt.get '/'
23
23
  response.status.should == 404
24
24
  end
25
-
25
+
26
26
  it "handles a root rack call correctly" do
27
27
  app << {pattern: '/$', target: generic_handler}
28
28
  response = rt.get '/'
29
29
  response.status.should == 200
30
30
  end
31
-
31
+
32
32
  it "does not maintain state between requests" do
33
33
  app << {pattern: '/state', target: proc { |env| [200, {}, [@state = 1 + @state.to_i]] }}
34
34
  response = rt.get '/state'
@@ -36,7 +36,7 @@ module Scorched
36
36
  response = rt.get '/state'
37
37
  response.body.should == '1'
38
38
  end
39
-
39
+
40
40
  it "raises exception when invalid mapping hash given" do
41
41
  expect {
42
42
  app << {pattern: '/'}
@@ -46,14 +46,14 @@ module Scorched
46
46
  }.to raise_error(ArgumentError)
47
47
  end
48
48
  end
49
-
49
+
50
50
  describe "URL matching" do
51
51
  it 'always matches from the beginning of the URL' do
52
52
  app << {pattern: 'about', target: generic_handler}
53
53
  response = rt.get '/about'
54
54
  response.status.should == 404
55
55
  end
56
-
56
+
57
57
  it "matches eagerly by default" do
58
58
  req = nil
59
59
  app << {pattern: '/*', target: proc do |env|
@@ -62,7 +62,7 @@ module Scorched
62
62
  response = rt.get '/about'
63
63
  req.captures.should == ['about']
64
64
  end
65
-
65
+
66
66
  it "can be forced to match end of URL" do
67
67
  app << {pattern: '/about$', target: generic_handler}
68
68
  response = rt.get '/about/us'
@@ -71,7 +71,7 @@ module Scorched
71
71
  response = rt.get '/about/us'
72
72
  response.status.should == 200
73
73
  end
74
-
74
+
75
75
  it "unescapes all characters except for the forward-slash and percent sign" do
76
76
  app << {pattern: '/a (quite) big fish', target: generic_handler}
77
77
  rt.get('/a%20%28quite%29%20big%20fish').status.should == 200
@@ -81,7 +81,7 @@ module Scorched
81
81
  rt.get('/page%2Fabout').status.should == 200
82
82
  rt.get('/page/about').status.should == 404
83
83
  end
84
-
84
+
85
85
  it "unmatched path doesn't always begin with a forward slash" do
86
86
  gh = generic_handler
87
87
  app << {pattern: '/ab', target: Class.new(Scorched::Controller) do
@@ -91,7 +91,7 @@ module Scorched
91
91
  resp.status.should == 200
92
92
  resp.body.should == "ok"
93
93
  end
94
-
94
+
95
95
  it "unmatched path begins with forward slash if last match was up to or included a forward slash" do
96
96
  gh = generic_handler
97
97
  app << {pattern: '/about/', target: Class.new(Scorched::Controller) do
@@ -103,7 +103,7 @@ module Scorched
103
103
  rt.get('/about/us').body.should == "ok"
104
104
  rt.get('/contact/us').body.should == "ok"
105
105
  end
106
-
106
+
107
107
  it "can match anonymous wildcards" do
108
108
  req = nil
109
109
  app << {pattern: '/anon/*/**', target: proc do |env|
@@ -112,7 +112,7 @@ module Scorched
112
112
  response = rt.get '/anon/jeff/has/crabs'
113
113
  req.captures.should == ['jeff', 'has/crabs']
114
114
  end
115
-
115
+
116
116
  it "can match named wildcards (ignoring anonymous captures)" do
117
117
  req = nil
118
118
  app << {pattern: '/anon/:name/*/::infliction', target: proc do |env|
@@ -121,7 +121,7 @@ module Scorched
121
121
  response = rt.get '/anon/jeff/smith/has/crabs'
122
122
  req.captures.should == {name: 'jeff', infliction: 'has/crabs'}
123
123
  end
124
-
124
+
125
125
  example "wildcards match one or more characters" do
126
126
  app << {pattern: '/*', target: proc { |env| [200, {}, ['ok']] }}
127
127
  rt.get('/').status.should == 404
@@ -139,7 +139,7 @@ module Scorched
139
139
  rt.get('/').status.should == 404
140
140
  rt.get('/dog/cat').status.should == 200
141
141
  end
142
-
142
+
143
143
  example "wildcards can optionally match zero or more characters" do
144
144
  app << {pattern: '/*?', target: proc { |env| [200, {}, ['ok']] }}
145
145
  rt.get('/').status.should == 200
@@ -157,7 +157,7 @@ module Scorched
157
157
  rt.get('/').status.should == 200
158
158
  rt.get('/dog/cat').status.should == 200
159
159
  end
160
-
160
+
161
161
  it "can match regex and preserve anonymous captures" do
162
162
  req = nil
163
163
  app << {pattern: %r{/anon/([^/]+)/(.+)}, target: proc do |env|
@@ -166,7 +166,7 @@ module Scorched
166
166
  response = rt.get '/anon/jeff/has/crabs'
167
167
  req.captures.should == ['jeff', 'has/crabs']
168
168
  end
169
-
169
+
170
170
  it "can match regex and preserve named captures (ignoring anonymous captures)" do
171
171
  req = nil
172
172
  app << {pattern: %r{/anon/(?<name>[^/]+)/([^/]+)/(?<infliction>.+)}, target: proc do |env|
@@ -175,7 +175,7 @@ module Scorched
175
175
  response = rt.get '/anon/jeff/smith/has/crabs'
176
176
  req.captures.should == {name: 'jeff', infliction: 'has/crabs'}
177
177
  end
178
-
178
+
179
179
  it "matches routes based on priority, otherwise giving precedence to those defined first" do
180
180
  order = []
181
181
  app << {pattern: '/', priority: -1, target: proc { |env| order << 'four'; [200, {}, ['ok']] }}
@@ -185,38 +185,35 @@ module Scorched
185
185
  rt.get('/').body.should == 'ok'
186
186
  order.should == %w{one two three four}
187
187
  end
188
-
189
- it "finds the best match for media type whilst respecting priority and definition order" do
190
- app << {pattern: '/', conditions: {media_type: 'text/html'}, target: proc { |env|
191
- [200, {}, ['text/html']]
192
- }}
193
- app << {pattern: '/', conditions: {media_type: 'application/json'}, target: proc { |env|
194
- [200, {}, ['application/json']]
195
- }}
196
- app << {pattern: '/', priority: 1, target: proc { |env|
197
- [200, {}, ['anything']]
198
- }}
199
- rt.get('/', media_type: 'application/json, */*;q=0.5').body.should == 'anything'
188
+
189
+ it "finds the best match for the media type whilst respecting priority and definition order" do
190
+ app << {pattern: '/', target: proc { |env| [200, {}, ['anything']] }}
191
+ app << {pattern: '/', conditions: {media_type: 'application/json'}, target: proc { |env| [200, {}, ['application/json']] }}
192
+ app << {pattern: '/', conditions: {media_type: 'text/html'}, target: proc { |env| [200, {}, ['text/html']] }}
193
+ app << {pattern: '/', priority: 1, target: proc { |env| [200, {}, ['anything_high_priority']] }}
194
+ rt.get('/', {}, 'HTTP_ACCEPT' => 'application/json, */*;q=0.5').body.should == 'anything_high_priority'
200
195
  app.mappings.pop
201
- rt.get('/', {}, 'HTTP_ACCEPT' => 'text/html;q=0.5, application/json').body.should == 'application/json'
202
- rt.get('/', {}, 'HTTP_ACCEPT' => 'text/html, */*;q=0.5').body.should == 'text/html'
196
+ rt.get('/', {}, 'HTTP_ACCEPT' => 'application/json;q=0.5, text/html').body.should == 'text/html'
197
+ rt.get('/', {}, 'HTTP_ACCEPT' => 'application/json, */*').body.should == 'application/json'
198
+ rt.get('/', {}, 'HTTP_ACCEPT' => '*/*, text/*').body.should == 'text/html'
199
+ rt.get('/', {}, 'HTTP_ACCEPT' => '*/*').body.should == 'anything'
203
200
  end
204
201
  end
205
-
202
+
206
203
  describe "conditions" do
207
204
  it "contains a default set of conditions" do
208
205
  app.conditions.should be_a(Options)
209
206
  app.conditions.should include(:method, :media_type)
210
207
  app.conditions.each { |k,v| v.should be_a(Proc) }
211
208
  end
212
-
209
+
213
210
  it "executes route only if all conditions return true" do
214
211
  app << {pattern: '/$', conditions: {method: 'POST'}, target: generic_handler}
215
212
  response = rt.get "/"
216
213
  response.status.should be_between(400, 499)
217
214
  response = rt.post "/"
218
215
  response.status.should == 200
219
-
216
+
220
217
  app.conditions[:has_name] = proc { |name| request.GET['name'] }
221
218
  app << {pattern: '/about', conditions: {method: ['GET', 'POST'], has_name: 'Ronald'}, target: generic_handler}
222
219
  response = rt.get "/about"
@@ -224,28 +221,28 @@ module Scorched
224
221
  response = rt.get "/about", name: 'Ronald'
225
222
  response.status.should == 200
226
223
  end
227
-
224
+
228
225
  it "raises exception when condition doesn't exist or is invalid" do
229
226
  app << {pattern: '/', conditions: {surprise_christmas_turkey: true}, target: generic_handler}
230
227
  expect {
231
228
  rt.get "/"
232
229
  }.to raise_error(Scorched::Error)
233
230
  end
234
-
231
+
235
232
  it "falls through to next route when conditions are not met" do
236
233
  app << {pattern: '/', conditions: {method: 'POST'}, target: proc { |env| [200, {}, ['post']] }}
237
234
  app << {pattern: '/', conditions: {method: 'GET'}, target: proc { |env| [200, {}, ['get']] }}
238
235
  rt.get("/").body.should == 'get'
239
236
  rt.post("/").body.should == 'post'
240
237
  end
241
-
238
+
242
239
  it "inverts the conditions if it's referenced with a trailing exclamation mark" do
243
240
  app << {pattern: '/', conditions: {method!: 'GET'}, target: proc { |env| [200, {}, ['ok']] }}
244
241
  rt.get("/").status.should == 405
245
242
  rt.post("/").status.should == 200
246
243
  end
247
244
  end
248
-
245
+
249
246
  describe "route helpers" do
250
247
  it "allows end points to be defined more succinctly" do
251
248
  route_proc = app.route('/*', 2, method: 'GET') { |capture| capture }
@@ -253,7 +250,7 @@ module Scorched
253
250
  mapping.should == {pattern: mapping[:pattern], priority: 2, conditions: {method: 'GET'}, target: route_proc}
254
251
  rt.get('/about').body.should == 'about'
255
252
  end
256
-
253
+
257
254
  it "can provide a mapping proc without mapping it" do
258
255
  block = proc { |capture| capture }
259
256
  wrapped_block = app.route(&block)
@@ -262,32 +259,32 @@ module Scorched
262
259
  app << {pattern: '/*', target: wrapped_block}
263
260
  rt.get('/turkey').body.should == 'turkey'
264
261
  end
265
-
262
+
266
263
  it "provides a method for every HTTP method" do
267
264
  [:get, :post, :put, :delete, :options, :head, :patch].each do |m|
268
265
  app.send(m, '/say_cool') { 'cool' }
269
266
  rt.send(m, '/say_cool').body.should == (m == :head ? '' : 'cool')
270
267
  end
271
268
  end
272
-
269
+
273
270
  it "provides wildcard captures as arguments" do
274
271
  app.get('/*/**') { |a,b| "#{a} #{b}" }
275
272
  rt.get('/hello/there/dude').body.should == 'hello there/dude'
276
273
  end
277
-
274
+
278
275
  it "provides named captures as individual arguments for each value" do
279
276
  app.get('/:given_name') { |a| a }
280
277
  app.get('/:given_name/::surname') { |a,b| "#{a} #{b}" }
281
278
  rt.get('/bob').body.should == 'bob'
282
279
  rt.get('/bob/smith').body.should == 'bob smith'
283
280
  end
284
-
281
+
285
282
  it "always matches to the end of the URL (implied $)" do
286
283
  app.get('/') { 'awesome '}
287
284
  rt.get('/dog').status.should == 404
288
285
  rt.get('/').status.should == 200
289
286
  end
290
-
287
+
291
288
  it "leaves body empty if nil is returned" do
292
289
  app.get('/') { }
293
290
  app.after do
@@ -295,7 +292,7 @@ module Scorched
295
292
  end
296
293
  rt.get('/')
297
294
  end
298
-
295
+
299
296
  it "can take an array of patterns" do
300
297
  app.get(['/', '/dog']) { 'rad' }
301
298
  rt.get('/dog').status.should == 200
@@ -303,7 +300,7 @@ module Scorched
303
300
  rt.get('/cat').status.should == 404
304
301
  end
305
302
  end
306
-
303
+
307
304
  describe "sub-controllers" do
308
305
  it "should ignore the already matched portions of the path" do
309
306
  app << {pattern: '/article', target: Class.new(Scorched::Controller) do
@@ -321,7 +318,7 @@ module Scorched
321
318
  'hello'
322
319
  end
323
320
  end
324
-
321
+
325
322
  resp = rt.get('/article/name')
326
323
  resp.status.should == 200
327
324
  outer_env['SCRIPT_NAME'].should == ''
@@ -329,7 +326,7 @@ module Scorched
329
326
  inner_env['SCRIPT_NAME'].should == '/article'
330
327
  inner_env['PATH_INFO'].should == '/name'
331
328
  end
332
-
329
+
333
330
  example "PATH_INFO and SCRIPT_NAME joined, should produce a full path" do
334
331
  app.controller '/article/' do
335
332
  get '/name' do
@@ -354,7 +351,7 @@ module Scorched
354
351
  response.status.should == 200
355
352
  response.body.should == 'hello'
356
353
  end
357
-
354
+
358
355
  it "can be given a pattern" do
359
356
  app.controller '/dog' do
360
357
  get('/') { 'roof' }
@@ -363,12 +360,12 @@ module Scorched
363
360
  response.status.should == 200
364
361
  response.body.should == 'roof'
365
362
  end
366
-
363
+
367
364
  it "inherits from parent class, or otherwise the specified class" do
368
365
  app.controller{}.superclass.should == app
369
366
  app.controller('/', String){}.superclass.should == String
370
367
  end
371
-
368
+
372
369
  it "can take mapping options" do
373
370
  app.controller priority: -1, conditions: {method: 'POST'} do
374
371
  route('/') { 'ok' }
@@ -377,7 +374,7 @@ module Scorched
377
374
  rt.get('/').status.should be_between(400, 499)
378
375
  rt.post('/').body.should == 'ok'
379
376
  end
380
-
377
+
381
378
  it "automatically passes to the outer controller when no match" do
382
379
  filters_run = 0
383
380
  app.controller do
@@ -389,7 +386,7 @@ module Scorched
389
386
  rt.get('/').body.should == 'hello'
390
387
  filters_run.should == 0
391
388
  end
392
-
389
+
393
390
  it "can be used to map a predefined controller" do
394
391
  person_controller = Class.new(Scorched::Controller) do
395
392
  get('/name') { 'George' }
@@ -399,7 +396,7 @@ module Scorched
399
396
  end
400
397
  end
401
398
  end
402
-
399
+
403
400
  describe "before/after filters" do
404
401
  they "run directly before and after the target action" do
405
402
  order = []
@@ -409,7 +406,7 @@ module Scorched
409
406
  rt.get('/')
410
407
  order.should == [:before, :action, :after]
411
408
  end
412
-
409
+
413
410
  they "run in the context of the controller (same as the route)" do
414
411
  route_instance = nil
415
412
  before_instance = nil
@@ -421,7 +418,7 @@ module Scorched
421
418
  route_instance.should == before_instance
422
419
  route_instance.should == after_instance
423
420
  end
424
-
421
+
425
422
  they "should run even if no route matches" do
426
423
  counter = 0
427
424
  app.before { counter += 1 }
@@ -429,7 +426,7 @@ module Scorched
429
426
  rt.delete('/').status.should == 404
430
427
  counter.should == 2
431
428
  end
432
-
429
+
433
430
  they "can take an optional set of conditions" do
434
431
  counter = 0
435
432
  app.before(method: ['GET', 'PUT']) { counter += 1 }
@@ -439,7 +436,7 @@ module Scorched
439
436
  rt.put('/')
440
437
  counter.should == 4
441
438
  end
442
-
439
+
443
440
  they "execute in the order they're defined" do
444
441
  order = []
445
442
  app.before { order << :first }
@@ -449,7 +446,7 @@ module Scorched
449
446
  rt.get('/')
450
447
  order.should == %i{first second third fourth}
451
448
  end
452
-
449
+
453
450
  describe "nesting" do
454
451
  example "filters inherit but only run once" do
455
452
  before_counter, after_counter = 0, 0
@@ -458,18 +455,18 @@ module Scorched
458
455
  subcontroller = app.controller { get('/') { 'wow' } }
459
456
  subcontroller.filters[:before].should == app.filters[:before]
460
457
  subcontroller.filters[:after].should == app.filters[:after]
461
-
458
+
462
459
  rt.get('/')
463
460
  before_counter.should == 1
464
461
  after_counter.should == 1
465
-
462
+
466
463
  # Hitting the subcontroller directly should yield the same results.
467
464
  before_counter, after_counter = 0, 0
468
465
  Rack::Test::Session.new(subcontroller).get('/')
469
466
  before_counter.should == 1
470
467
  after_counter.should == 1
471
468
  end
472
-
469
+
473
470
  example "before filters run from outermost to innermost" do
474
471
  order = []
475
472
  app.before { order << :outer }
@@ -482,7 +479,7 @@ module Scorched
482
479
  rt.get('/')
483
480
  order.should == %i{outer outer2 inner inner2}
484
481
  end
485
-
482
+
486
483
  example "after filters run from innermost to outermost" do
487
484
  order = []
488
485
  app.after { order << :outer }
@@ -495,7 +492,7 @@ module Scorched
495
492
  rt.get('/')
496
493
  order.should == %i{inner inner2 outer outer2}
497
494
  end
498
-
495
+
499
496
  example "inherited filters which fail to satisfy their conditions are re-evaluated at every level" do
500
497
  order = []
501
498
  sub_class = app.controller do
@@ -518,7 +515,7 @@ module Scorched
518
515
  end
519
516
  end
520
517
  end
521
-
518
+
522
519
  describe "error filters" do
523
520
  let(:app) do
524
521
  Class.new(Scorched::Controller) do
@@ -527,26 +524,26 @@ module Scorched
527
524
  end
528
525
  end
529
526
  end
530
-
527
+
531
528
  they "catch exceptions" do
532
529
  app.error { response.status = 500 }
533
530
  rt.get('/').status.should == 500
534
531
  end
535
-
532
+
536
533
  they "receive the exception object as their first argument" do
537
534
  error = nil
538
535
  app.error { |e| error = e }
539
536
  rt.get('/')
540
537
  error.should be_a(StandardError)
541
538
  end
542
-
539
+
543
540
  they "try the next handler if the previous handler returns false" do
544
541
  handlers_called = 0
545
542
  app.error { handlers_called += 1 }
546
543
  app.error { handlers_called += 1 }
547
544
  rt.get '/'
548
545
  handlers_called.should == 1
549
-
546
+
550
547
  app.error_filters.clear
551
548
  handlers_called = 0
552
549
  app.error { handlers_called += 1; false }
@@ -554,45 +551,45 @@ module Scorched
554
551
  rt.get '/'
555
552
  handlers_called.should == 2
556
553
  end
557
-
554
+
558
555
  they "still runs after filters if route error is handled" do
559
556
  app.after { response.status = 111 }
560
557
  app.error { true }
561
558
  rt.get('/').status.should == 111
562
559
  end
563
-
560
+
564
561
  they "can handle exceptions in before/after filters" do
565
562
  app.error { |e| response.write e.class.name }
566
563
  app.after { raise ArgumentError }
567
564
  rt.get('/').body.should == 'StandardErrorArgumentError'
568
565
  end
569
-
566
+
570
567
  they "swallow halts when executed in an outer context" do
571
568
  app.before { raise "Big bad error" }
572
569
  app.error { throw :halt }
573
570
  rt.get('/') # Would otherwise bomb out with uncaught throw.
574
571
  end
575
-
572
+
576
573
  they "only get called once per error" do
577
574
  times_called = 0
578
575
  app.error { times_called += 1 }
579
576
  rt.get '/'
580
577
  times_called.should == 1
581
578
  end
582
-
579
+
583
580
  they "fall through when unhandled" do
584
581
  expect {
585
582
  rt.get '/'
586
583
  }.to raise_error(StandardError)
587
584
  end
588
-
585
+
589
586
  they "can optionally filter on one or more exception types" do
590
587
  app.get('/arg_error') { raise ArgumentError }
591
-
588
+
592
589
  app.error(StandardError, ArgumentError) { true }
593
590
  rt.get '/'
594
591
  rt.get '/arg_error'
595
-
592
+
596
593
  app.error_filters.clear
597
594
  app.error(ArgumentError) { true }
598
595
  expect {
@@ -600,7 +597,7 @@ module Scorched
600
597
  }.to raise_error(StandardError)
601
598
  rt.get '/arg_error'
602
599
  end
603
-
600
+
604
601
  they "can take an optional set of conditions" do
605
602
  app.error(method: ['GET', 'PUT']) { true }
606
603
  expect {
@@ -610,7 +607,7 @@ module Scorched
610
607
  rt.put('/')
611
608
  end
612
609
  end
613
-
610
+
614
611
  describe "middleware" do
615
612
  let(:app) do
616
613
  Class.new(Scorched::Controller) do
@@ -625,56 +622,56 @@ module Scorched
625
622
  end
626
623
  end
627
624
  end
628
-
625
+
629
626
  it "is only included once by default" do
630
627
  rt.get('/').body.should == '1'
631
628
  rt.get('/sub_controller').body.should == '1'
632
629
  end
633
-
630
+
634
631
  it "can be explicitly included more than once in sub-controllers" do
635
632
  app.mappings[-1][:target].middleware << proc { use Scorched::SimpleCounter }
636
633
  rt.get('/').body.should == '1'
637
634
  rt.get('/sub_controller').body.should == '2'
638
635
  end
639
636
  end
640
-
637
+
641
638
  describe "halting" do
642
639
  it "short circuits current request" do
643
640
  has_run = false
644
641
  app.get('/') { halt; has_run = true }
645
642
  rt.get '/'
646
- has_run.should be_false
643
+ has_run.should be_falsey
647
644
  end
648
-
645
+
649
646
  it "takes an optional status" do
650
647
  app.get('/') { halt 600 }
651
648
  rt.get('/').status.should == 600
652
649
  end
653
-
650
+
654
651
  it "takes an optional response body" do
655
652
  app.get('/') { halt 'cool' }
656
653
  rt.get('/').body.should == 'cool'
657
654
  end
658
-
655
+
659
656
  it "can take a status and a response body" do
660
657
  app.get('/') { halt 600, 'cool' }
661
658
  rt.get('/').status.should == 600
662
659
  rt.get('/').body.should == 'cool'
663
660
  end
664
-
661
+
665
662
  it "still processes filters" do
666
663
  app.after { response.status = 600 }
667
664
  app.get('/') { halt }
668
665
  rt.get('/').status.should == 600
669
666
  end
670
-
667
+
671
668
  describe "within filters" do
672
669
  it "short circuits filters if halted within filter" do
673
670
  app.before { halt }
674
671
  app.after { response.status = 600 }
675
672
  rt.get('/').status.should_not == 600
676
673
  end
677
-
674
+
678
675
  it "forced filters are always run" do
679
676
  app.before { halt }
680
677
  app.after(force: true) { response.status = 600 }
@@ -682,7 +679,7 @@ module Scorched
682
679
  app.get('/') { 'hello' }
683
680
  rt.get('/').status.should == 600
684
681
  end
685
-
682
+
686
683
  it "halting within a forced filter still runs other forced filters" do
687
684
  app.before { halt }
688
685
  app.before(force: true) { halt }
@@ -694,7 +691,7 @@ module Scorched
694
691
  end
695
692
  end
696
693
  end
697
-
694
+
698
695
  describe 'redirecting' do
699
696
  it "redirects using 303 or 302 by default, depending on HTTP version" do
700
697
  app.get('/cat') { redirect '/dog' }
@@ -705,12 +702,12 @@ module Scorched
705
702
  response.status.should == 302
706
703
  response.location.should == '/dog'
707
704
  end
708
-
705
+
709
706
  it "allows the HTTP status to be overridden" do
710
707
  app.get('/') { redirect '/somewhere', 308 }
711
708
  rt.get('/').status.should == 308
712
709
  end
713
-
710
+
714
711
  it "halts the request after redirect" do
715
712
  var = false
716
713
  app.get('/') do
@@ -720,7 +717,7 @@ module Scorched
720
717
  rt.get('/')
721
718
  var.should == false
722
719
  end
723
-
720
+
724
721
  it "works in filters" do
725
722
  app.error { redirect '/somewhere' }
726
723
  app.get('/') { raise "Some error" }
@@ -729,7 +726,7 @@ module Scorched
729
726
  rt.get('/').location.should == '/somewhere_else'
730
727
  end
731
728
  end
732
-
729
+
733
730
  describe "passing" do
734
731
  it "invokes the next match" do
735
732
  app.get('/') { response.body << 'hello'; pass }
@@ -738,7 +735,7 @@ module Scorched
738
735
  app.get('/') { response.body << '!' } # Shouldn't be hit
739
736
  rt.get('/').body.should == 'hello there sir'
740
737
  end
741
-
738
+
742
739
  it "invokes the next match in parent controller if passed from filter" do
743
740
  effects = []
744
741
  app.controller '/sub' do
@@ -756,7 +753,7 @@ module Scorched
756
753
  rt.get('/sub').body.should == 'y'
757
754
  effects.should == [1, 2]
758
755
  end
759
-
756
+
760
757
  it "results in uncaught symbol if passing within filter of root controller " do
761
758
  app.before { pass }
762
759
  expect {
@@ -764,36 +761,36 @@ module Scorched
764
761
  rt.get('/')
765
762
  }.to raise_error(ArgumentError)
766
763
  end
767
-
764
+
768
765
  it "is not considered a match if a mapping passes the request" do
769
766
  app.get('/*') { pass }
770
767
  app.get('/nopass') { }
771
768
  handled = nil
772
769
  app.after { handled = @_handled }
773
770
  rt.get('/').status.should == 404 # 404 if matched, but passed
774
- handled.should_not be_true
771
+ handled.should be_falsey
775
772
  rt.get('/nopass').status.should == 200
776
- handled.should be_true
773
+ handled.should be_truthy
777
774
  end
778
775
  end
779
-
776
+
780
777
  describe "status codes" do
781
778
  it "returns 405 when :method condition fails" do
782
779
  app.get('/') { }
783
780
  rt.post('/').status.should == 405
784
781
  end
785
-
782
+
786
783
  it "returns 404 when :host condition fails" do
787
784
  app.get('/', host: 'somehost') { }
788
785
  rt.get('/').status.should == 404
789
786
  end
790
-
787
+
791
788
  it "returns 406 when accept-related conditions fail" do
792
789
  app.get('/media_type', media_type: 'application/json') { }
793
790
  app.get('/charset', charset: 'iso-8859-5') { }
794
791
  app.get('/encoding', encoding: 'gzip') { }
795
792
  app.get('/language', language: 'en') { }
796
-
793
+
797
794
  rt.get('/media_type', {}, 'HTTP_ACCEPT' => 'application/json').status.should == 200
798
795
  rt.get('/media_type', {}, 'HTTP_ACCEPT' => 'text/html').status.should == 406
799
796
  rt.get('/charset', {}, 'HTTP_ACCEPT_CHARSET' => 'iso-8859-5').status.should == 200
@@ -804,7 +801,7 @@ module Scorched
804
801
  rt.get('/language', {}, 'HTTP_ACCEPT_LANGUAGE' => 'da').status.should == 406
805
802
  end
806
803
  end
807
-
804
+
808
805
  describe "configuration" do
809
806
  describe :strip_trailing_slash do
810
807
  it "can be set to strip trailing slash and redirect" do
@@ -814,7 +811,7 @@ module Scorched
814
811
  response.status.should == 307
815
812
  response['Location'].should == '/test'
816
813
  end
817
-
814
+
818
815
  it "can be set to ignore trailing slash while pattern matching" do
819
816
  app.config[:strip_trailing_slash] = :ignore
820
817
  hit = false
@@ -822,17 +819,17 @@ module Scorched
822
819
  rt.get('/test/').status.should == 200
823
820
  hit.should == true
824
821
  end
825
-
822
+
826
823
  it "can be set not do nothing with a trailing slash" do
827
824
  app.config[:strip_trailing_slash] = false
828
825
  app.get('/test') { }
829
826
  rt.get('/test/').status.should == 404
830
-
827
+
831
828
  app.get('/test/') { }
832
829
  rt.get('/test/').status.should == 200
833
830
  end
834
831
  end
835
-
832
+
836
833
  describe :static_dir do
837
834
  it "can serve static file from the specific directory" do
838
835
  app.config[:static_dir] = 'public'
@@ -840,23 +837,23 @@ module Scorched
840
837
  response.status.should == 200
841
838
  response.body.should == 'My static file!'
842
839
  end
843
-
840
+
844
841
  it "can be disabled" do
845
842
  app.config[:static_dir] = false
846
843
  response = rt.get('/static.txt')
847
844
  response.status.should == 404
848
845
  end
849
846
  end
850
-
847
+
851
848
  describe :show_exceptions do
852
849
  it "shows debug-friendly error page for unhandled exceptions" do
853
850
  app.config[:show_exceptions] = true
854
851
  app.get('/') { raise RuntimeError, "Kablamo!" }
855
- response = rt.get('/')
852
+ response = rt.get('/', {}, 'HTTP_ACCEPT' => 'text/html')
856
853
  response.status.should == 500
857
854
  response.body.should include('Rack::ShowExceptions')
858
855
  end
859
-
856
+
860
857
  it "can be disabled" do
861
858
  app.config[:show_exceptions] = false
862
859
  app.get('/') { raise RuntimeError, "Kablamo!" }
@@ -865,7 +862,7 @@ module Scorched
865
862
  }.to raise_error(RuntimeError)
866
863
  end
867
864
  end
868
-
865
+
869
866
  describe :show_http_error_pages do
870
867
  it "shows HTTP error pages for errors 400 to 599" do
871
868
  app.config[:show_http_error_pages] = true
@@ -875,7 +872,7 @@ module Scorched
875
872
  rt.post('/').body.should include('405 Method Not Allowed')
876
873
  rt.get('/unknown').body.should include('480 ')
877
874
  end
878
-
875
+
879
876
  it "can be disabled" do
880
877
  app.config[:show_http_error_pages] = false
881
878
  app.get('/') { response.status = 501; '' }
@@ -885,7 +882,7 @@ module Scorched
885
882
  rt.post('/unknown').body.should_not include('408 ')
886
883
  end
887
884
  end
888
-
885
+
889
886
  describe :auto_pass do
890
887
  it "passes to the outer controller without running any filters, if no match" do
891
888
  sub = Class.new(Scorched::Controller) do
@@ -900,22 +897,22 @@ module Scorched
900
897
  rt.get('/').status.should == 200
901
898
  rt.get('/hello').body.should == 'hello'
902
899
  rt.get('/hello').status.should == 600
903
-
900
+
904
901
  sub.config[:auto_pass] = false
905
902
  rt.get('/').body.should == ''
906
903
  rt.get('/').status.should == 600
907
904
  end
908
905
  end
909
-
906
+
910
907
  describe :cache_templates do
911
908
  before(:each) do
912
909
  File.open('views/temp.str', 'w') { |f| f.write 'hello world' }
913
910
  end
914
-
911
+
915
912
  after(:all) {
916
913
  File.unlink 'views/temp.str'
917
914
  }
918
-
915
+
919
916
  it "can cache templates" do
920
917
  app.config[:cache_templates] = true
921
918
  app.get('/') { render :'temp.str' }
@@ -923,7 +920,7 @@ module Scorched
923
920
  File.open('views/temp.str', 'a') { |f| f.write '!!!' }
924
921
  rt.get('/').body.should == 'hello world'
925
922
  end
926
-
923
+
927
924
  it "can be set not to cache templates" do
928
925
  app.config[:cache_templates] = false
929
926
  app.get('/') { render :'temp.str' }
@@ -933,7 +930,7 @@ module Scorched
933
930
  end
934
931
  end
935
932
  end
936
-
933
+
937
934
  describe "sessions" do
938
935
  it "provides convenience method for accessing the Rack session" do
939
936
  rack_session = nil
@@ -944,49 +941,49 @@ module Scorched
944
941
  rt.get('/')
945
942
  rack_session.should be_a(Rack::Session::Abstract::SessionHash)
946
943
  end
947
-
944
+
948
945
  describe "flash" do
949
946
  before(:each) do
950
947
  app.middleware << proc { use Rack::Session::Cookie, secret: 'test' }
951
948
  end
952
-
949
+
953
950
  it "keeps session variables that live for one page load" do
954
951
  app.get('/set') { flash[:cat] = 'meow' }
955
952
  app.get('/get') { flash[:cat] }
956
-
953
+
957
954
  rt.get('/set')
958
955
  rt.get('/get').body.should == 'meow'
959
956
  rt.get('/get').body.should == ''
960
957
  end
961
-
958
+
962
959
  it "always reads from the original request flash" do
963
960
  app.get('/') do
964
961
  flash[:counter] = flash[:counter] ? flash[:counter] + 1 : 0
965
962
  flash[:counter].to_s
966
963
  end
967
-
964
+
968
965
  rt.get('/').body.should == ''
969
966
  rt.get('/').body.should == '0'
970
967
  rt.get('/').body.should == '1'
971
968
  end
972
-
969
+
973
970
  it "can only remove flash variables if the flash object is accessed" do
974
971
  app.get('/set') { flash[:cat] = 'meow' }
975
972
  app.get('/get') { flash[:cat] }
976
973
  app.get('/null') { }
977
-
974
+
978
975
  rt.get('/set')
979
976
  rt.get('/null')
980
977
  rt.get('/get').body.should == 'meow'
981
978
  rt.get('/get').body.should == ''
982
979
  end
983
-
980
+
984
981
  it "can keep multiple sets of flash session variables" do
985
982
  app.get('/set_animal') { flash(:animals)[:cat] = 'meow' }
986
983
  app.get('/get_animal') { flash(:animals)[:cat] }
987
984
  app.get('/set_name') { flash(:names)[:jeff] = 'male' }
988
985
  app.get('/get_name') { flash(:names)[:jeff] }
989
-
986
+
990
987
  rt.get('/set_animal')
991
988
  rt.get('/set_name')
992
989
  rt.get('/get_animal').body.should == 'meow'
@@ -996,7 +993,7 @@ module Scorched
996
993
  end
997
994
  end
998
995
  end
999
-
996
+
1000
997
  describe "cookie helper" do
1001
998
  it "sets, retrieves and deletes cookies" do
1002
999
  app.get('/') { cookie :test }
@@ -1004,7 +1001,7 @@ module Scorched
1004
1001
  app.post('/goodbye') { cookie :test, {value: 'goodbye', expires: Time.now() + 999999 } }
1005
1002
  app.delete('/') { cookie :test, nil }
1006
1003
  app.delete('/alt') { cookie :test, {value: nil} }
1007
-
1004
+
1008
1005
  rt.get('/').body.should == ''
1009
1006
  rt.post('/')
1010
1007
  rt.get('/').body.should == 'hello'
@@ -1016,7 +1013,7 @@ module Scorched
1016
1013
  rt.get('/').body.should == ''
1017
1014
  end
1018
1015
  end
1019
-
1016
+
1020
1017
  describe "rendering" do
1021
1018
  before(:each) do
1022
1019
  app.render_defaults.each { |k,v| app.render_defaults[k] = nil }
@@ -1042,7 +1039,7 @@ module Scorched
1042
1039
  end
1043
1040
  rt.get('/')
1044
1041
  end
1045
-
1042
+
1046
1043
  it "properly respects absolute and relative file paths in respect to the view directory" do
1047
1044
  app.get('/relative') do
1048
1045
  render(:'../views/main.erb', dir: 'views')
@@ -1105,14 +1102,14 @@ module Scorched
1105
1102
  end
1106
1103
  rt.get('/').body.should == '({1 for none}{1 for none})'
1107
1104
  end
1108
-
1105
+
1109
1106
  it "can pass local variables through to view" do
1110
1107
  app.get '/' do
1111
1108
  render '<%= var %>', engine: 'erb', dir: 'views', locals: {var: 'hello sailor'}
1112
1109
  end
1113
1110
  rt.get('/').body.should == 'hello sailor'
1114
1111
  end
1115
-
1112
+
1116
1113
  it "provides a means for passing options directly to tilt" do
1117
1114
  Tilt.register(Class.new(Tilt::ERBTemplate) do
1118
1115
  def prepare
@@ -1120,13 +1117,13 @@ module Scorched
1120
1117
  super
1121
1118
  end
1122
1119
  end, 'test')
1123
-
1120
+
1124
1121
  app.get '/safe' do
1125
1122
  render '<%= var %>', engine: 'test', dir: 'views', locals: {var: 'hello sailor'}
1126
1123
  render '<%= var %>', engine: 'test', dir: 'views', locals: {var: 'hello sailor'}, tilt: {engine: Class.new}
1127
1124
  end
1128
1125
  rt.get('/safe').body.should == 'hello sailor'
1129
-
1126
+
1130
1127
  app.get '/boomer' do
1131
1128
  render '<%= var %>', engine: 'test', dir: 'views', locals: {var: 'hello sailor'}, tilt: {engine: 'invalid'}
1132
1129
  end
@@ -1135,12 +1132,12 @@ module Scorched
1135
1132
  }.to raise_error(NoMethodError)
1136
1133
  end
1137
1134
  end
1138
-
1135
+
1139
1136
  describe "url helpers" do
1140
1137
  let(:my_app) do
1141
1138
  Class.new(Scorched::Controller)
1142
1139
  end
1143
-
1140
+
1144
1141
  let(:root_app) do
1145
1142
  Class.new(Scorched::Controller)
1146
1143
  end
@@ -1152,7 +1149,7 @@ module Scorched
1152
1149
  builder.map('/') { run this.root_app }
1153
1150
  builder.to_app
1154
1151
  end
1155
-
1152
+
1156
1153
  it "can determine the root path of the current Scorched application" do
1157
1154
  my_app.controller '/article' do
1158
1155
  get '/name' do
@@ -1161,26 +1158,26 @@ module Scorched
1161
1158
  end
1162
1159
  rt.get('/myapp/article/name').body.should == '/myapp'
1163
1160
  end
1164
-
1161
+
1165
1162
  describe "url" do
1166
1163
  it "returns the fully qualified URL" do
1167
1164
  my_app.get('/') { url }
1168
1165
  rt.get('https://scorchedrb.com:73/myapp?something=true').body.should ==
1169
1166
  'https://scorchedrb.com:73/myapp'
1170
1167
  end
1171
-
1168
+
1172
1169
  it "can append an optional path" do
1173
1170
  my_app.get('/') { url('hello') }
1174
1171
  rt.get('https://scorchedrb.com:73/myapp?something=true').body.should ==
1175
1172
  'https://scorchedrb.com:73/myapp/hello'
1176
1173
  end
1177
-
1174
+
1178
1175
  it "returns the given URL if scheme detected" do
1179
1176
  test_url = 'http://google.com/blah'
1180
1177
  my_app.get('/') { url(test_url) }
1181
1178
  rt.get('/myapp').body.should == test_url
1182
1179
  end
1183
-
1180
+
1184
1181
  it "generates URL from inside subcontroller defined with controller helper" do
1185
1182
  root_app.controller '/sub2' do
1186
1183
  get('/') { url('hi') }
@@ -1188,29 +1185,29 @@ module Scorched
1188
1185
  rt.get('https://scorchedrb.com:73/sub2').body.should == 'https://scorchedrb.com:73/hi'
1189
1186
  end
1190
1187
  end
1191
-
1188
+
1192
1189
  describe "absolute" do
1193
1190
  it "returns an absolute URL path" do
1194
1191
  my_app.get('/absolute') { absolute }
1195
1192
  rt.get('http://scorchedrb.com/myapp/absolute?something=true').body.should == '/myapp'
1196
1193
  end
1197
-
1194
+
1198
1195
  it "returns a forward slash if script name is the root of the URL path" do
1199
1196
  root_app.get('/') { absolute }
1200
1197
  rt.get('http://scorchedrb.com').body.should == '/'
1201
1198
  end
1202
-
1199
+
1203
1200
  it "can append an optional path" do
1204
1201
  my_app.get('/absolute') { absolute('hello') }
1205
1202
  rt.get('http://scorchedrb.com/myapp/absolute?something=true').body.should == '/myapp/hello'
1206
1203
  end
1207
-
1204
+
1208
1205
  it "returns the given URL if scheme detected" do
1209
1206
  test_url = 'http://google.com/blah'
1210
1207
  my_app.get('/') { absolute(test_url) }
1211
1208
  rt.get('/myapp').body.should == test_url
1212
1209
  end
1213
-
1210
+
1214
1211
  it "returns an absolute URL path for subcontroller defined with controller helper" do
1215
1212
  root_app.controller '/sub2' do
1216
1213
  get('/') { absolute }
@@ -1219,7 +1216,7 @@ module Scorched
1219
1216
  end
1220
1217
  end
1221
1218
  end
1222
-
1219
+
1223
1220
  describe "delegators" do
1224
1221
  it "delegates captures" do
1225
1222
  app.get('/:id') { captures[:id] }