regal 0.1.0 → 0.2.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.
@@ -47,6 +47,8 @@ module Regal
47
47
  it 'responds with 404 when the path does not match any route' do
48
48
  get '/hello/fnord'
49
49
  expect(last_response.status).to eq(404)
50
+ get '/foo/bar/baz/qux'
51
+ expect(last_response.status).to eq(404)
50
52
  end
51
53
 
52
54
  it 'responds with 405 when the path matches a route but there is no handler for the HTTP method' do
@@ -129,10 +131,31 @@ module Regal
129
131
  end
130
132
 
131
133
  context 'an app doing work before route handlers' do
132
- let :app do
133
- App.new do
134
+ MountedBeforeApp = App.create do
135
+ before do |request|
136
+ request.attributes[:some_key] << 2
137
+ end
138
+
139
+ route 'in-mounted-app' do
134
140
  before do |request|
141
+ request.attributes[:some_key] << 3
142
+ end
143
+
144
+ get do |request|
145
+ request.attributes[:some_key].join(',')
146
+ end
147
+ end
148
+ end
149
+
150
+ let :state do
151
+ {}
152
+ end
153
+
154
+ let :app do
155
+ App.new(state: state) do
156
+ before do |request, _|
135
157
  request.attributes[:some_key] = [1]
158
+ request.attributes[:state][:before] = :called
136
159
  end
137
160
 
138
161
  get do |request|
@@ -185,7 +208,8 @@ module Regal
185
208
  response.body = 'whoopiedoo'
186
209
  end
187
210
 
188
- get do
211
+ get do |_, response|
212
+ response.headers['X-HandlerCalled'] = 'yes'
189
213
  "I'm not called!"
190
214
  end
191
215
 
@@ -193,6 +217,8 @@ module Regal
193
217
  response.headers['WasAfterCalled'] = 'yes'
194
218
  end
195
219
  end
220
+
221
+ mount MountedBeforeApp
196
222
  end
197
223
  end
198
224
 
@@ -221,6 +247,14 @@ module Regal
221
247
  expect(last_response.status).to eq(307)
222
248
  end
223
249
 
250
+ context 'when the path does not match any route' do
251
+ it 'does not run any before blocks' do
252
+ get '/does-not-exist'
253
+ expect(last_response.status).to eq(404)
254
+ expect(state).to be_empty
255
+ end
256
+ end
257
+
224
258
  context 'when the response is marked as finished' do
225
259
  before do
226
260
  get '/redirect-before'
@@ -228,20 +262,49 @@ module Regal
228
262
 
229
263
  it 'does not call further handlers or before blocks when the response is marked as finished' do
230
264
  expect(last_response.body).to eq('Go somewhere else')
265
+ expect(last_response.headers).to_not have_key('X-HandlerCalled')
231
266
  end
232
267
 
233
268
  it 'calls after blocks' do
234
269
  expect(last_response.headers).to include('WasAfterCalled' => 'yes')
235
270
  end
236
271
  end
272
+
273
+ context 'with a mounted app' do
274
+ it 'runs the before blocks from both the mounting and the mounted app' do
275
+ get '/in-mounted-app'
276
+ expect(last_response.body).to eq('1,2,3')
277
+ end
278
+ end
237
279
  end
238
280
 
239
281
  context 'an app doing work after route handlers' do
240
- let :app do
241
- App.new do
282
+ MountedAfterApp = App.create do
283
+ after do |_, response|
284
+ response.body['list'] << 1
285
+ end
286
+
287
+ route 'in-mounted-app' do
242
288
  after do |_, response|
289
+ response.body['list'] << 2
290
+ end
291
+
292
+ get do
293
+ {'list' => []}
294
+ end
295
+ end
296
+ end
297
+
298
+ let :state do
299
+ {}
300
+ end
301
+
302
+ let :app do
303
+ App.new(state: state) do
304
+ after do |request, response|
243
305
  response.headers['Content-Type'] = 'application/json'
244
- response.body = JSON.dump(response.body)
306
+ response.body = response.body.is_a?(String) ? %("#{response.body}") : JSON.dump(response.body)
307
+ request.attributes[:state][:after] = :called
245
308
  end
246
309
 
247
310
  get do |request|
@@ -260,11 +323,11 @@ module Regal
260
323
 
261
324
  route 'two-after' do
262
325
  after do |request, response|
263
- response.body['list'] << 1
326
+ response.body['list'] << 2
264
327
  end
265
328
 
266
329
  after do |request, response|
267
- response.body['list'] << 2
330
+ response.body['list'] << 1
268
331
  end
269
332
 
270
333
  get do |request|
@@ -281,6 +344,74 @@ module Regal
281
344
  end
282
345
  end
283
346
  end
347
+
348
+ route 'stops-early' do
349
+ before do |_, response|
350
+ response.body = 'before1'
351
+ response.finish
352
+ end
353
+
354
+ after do |_, response|
355
+ response.body << '|after1'
356
+ end
357
+
358
+ route 'not-called' do
359
+ before do |_, response|
360
+ response.body << '|before2'
361
+ end
362
+
363
+ after do |_, response|
364
+ response.body << '|after2'
365
+ end
366
+
367
+ get do |_, response|
368
+ response.body << '|handler'
369
+ end
370
+ end
371
+ end
372
+
373
+ route 'raises' do
374
+ before do |_, response|
375
+ response.body = 'before1'
376
+ end
377
+
378
+ after do |_, response|
379
+ response.body << '|after1'
380
+ end
381
+
382
+ rescue_from RuntimeError do
383
+ end
384
+
385
+ route 'not-called' do
386
+ before do |_, response|
387
+ response.body << '|before2'
388
+ end
389
+
390
+ after do |_, response|
391
+ response.body << '|after2'
392
+ end
393
+
394
+ get do |_, response|
395
+ response.body << '|handler'
396
+ end
397
+ end
398
+
399
+ route 'raises' do
400
+ before do |_, response|
401
+ response.body << '|before2'
402
+ raise 'Burk!'
403
+ end
404
+
405
+ after do |_, response|
406
+ response.body << '|after2'
407
+ end
408
+
409
+ get do
410
+ end
411
+ end
412
+ end
413
+
414
+ mount MountedAfterApp
284
415
  end
285
416
  end
286
417
 
@@ -303,6 +434,112 @@ module Regal
303
434
  get '/two-after/another-after'
304
435
  expect(last_response.body).to eq('{"list":[3,2,1]}')
305
436
  end
437
+
438
+ context 'when the path does not match any route' do
439
+ it 'does not run any before blocks' do
440
+ get '/does-not-exist'
441
+ expect(last_response.status).to eq(404)
442
+ expect(state).to be_empty
443
+ end
444
+ end
445
+
446
+ context 'with a mounted app' do
447
+ it 'runs the after blocks from both the mounting and the mounted app' do
448
+ get '/in-mounted-app'
449
+ expect(last_response.body).to eq('{"list":[2,1]}')
450
+ end
451
+ end
452
+
453
+ context 'when the request is finished by a before block' do
454
+ it 'runs only the after blocks from the same level and up' do
455
+ get '/stops-early/not-called'
456
+ expect(last_response.body).to eq('"before1|after1"')
457
+ end
458
+ end
459
+
460
+ context 'when a before block raises an error and it is handled by a rescue block' do
461
+ it 'runs only the after blocks from the same level as the rescue block, and up' do
462
+ get '/raises/raises'
463
+ expect(last_response.body).to eq('"before1|before2|after1"')
464
+ end
465
+ end
466
+
467
+ context 'when rescue block raises an error that is handled a few levels up' do
468
+ let :app do
469
+ App.new do
470
+ after do |_, response|
471
+ response.body = response.body.join('|')
472
+ end
473
+
474
+ route 'one' do
475
+ after do |_, response|
476
+ response.body << 'after1'
477
+ end
478
+
479
+ route 'two' do
480
+ after do |_, response|
481
+ response.body << 'after2'
482
+ end
483
+
484
+ rescue_from StandardError do
485
+ end
486
+
487
+ route 'three' do
488
+ after do |_, response|
489
+ response.body << 'after3'
490
+ end
491
+
492
+ route 'four' do
493
+ after do |_, response|
494
+ response.body << 'after4'
495
+ end
496
+
497
+ rescue_from StandardError do
498
+ raise 'Snork'
499
+ end
500
+
501
+ route 'raise-in-after' do
502
+ after do |_, response|
503
+ raise 'Bork'
504
+ end
505
+
506
+ get do
507
+ []
508
+ end
509
+ end
510
+
511
+ route 'raise-in-before' do
512
+ before do |_, response|
513
+ response.body = []
514
+ raise 'Bork'
515
+ end
516
+
517
+ get do
518
+ end
519
+ end
520
+
521
+ route 'raise-in-handler' do
522
+ get do |_, response|
523
+ response.body = []
524
+ raise 'Bork'
525
+ end
526
+ end
527
+ end
528
+ end
529
+ end
530
+ end
531
+ end
532
+ end
533
+
534
+ it 'runs only the after blocks from the same level as the rescue block, and up' do
535
+ get '/one/two/three/four/raise-in-after'
536
+ expect(last_response.body).to eq('after2|after1')
537
+ get '/one/two/three/four/raise-in-before'
538
+ expect(last_response.body).to eq('after2|after1')
539
+ get '/one/two/three/four/raise-in-handler'
540
+ expect(last_response.body).to eq('after2|after1')
541
+ end
542
+ end
306
543
  end
307
544
 
308
545
  context 'an app that has capturing routes' do
@@ -378,6 +615,18 @@ module Regal
378
615
  end
379
616
  end
380
617
 
618
+ SimpleApp1 = App.create do
619
+ get do
620
+ 'simple1'
621
+ end
622
+ end
623
+
624
+ SimpleApp2 = App.create do
625
+ get do
626
+ 'simple2'
627
+ end
628
+ end
629
+
381
630
  let :app do
382
631
  App.new do
383
632
  route 'i' do
@@ -388,8 +637,26 @@ module Regal
388
637
  end
389
638
 
390
639
  route 'oh' do
640
+ get do
641
+ 'oh'
642
+ end
643
+
391
644
  mount HelloApp
392
645
  end
646
+
647
+ route 'shadows' do
648
+ get do
649
+ 'simple0'
650
+ end
651
+
652
+ mount SimpleApp1
653
+ mount SimpleApp2
654
+
655
+ route 'sub' do
656
+ mount SimpleApp1
657
+ mount SimpleApp2
658
+ end
659
+ end
393
660
  end
394
661
  end
395
662
 
@@ -416,42 +683,162 @@ module Regal
416
683
  expect(last_response.status).to eq(200)
417
684
  expect(last_response.body).to eq('hello')
418
685
  end
686
+
687
+ context 'when the mounting and the mounted apps have handlers that match a request' do
688
+ it 'calls the mounting app\'s handler' do
689
+ get '/shadows'
690
+ expect(last_response.status).to eq(200)
691
+ expect(last_response.body).to eq('simple0')
692
+ end
693
+ end
694
+
695
+ context 'when the mounted apps have handlers that match a request' do
696
+ it 'calls the last mounted app\'s handler', pending: true do
697
+ get '/shadows/sub'
698
+ expect(last_response.status).to eq(200)
699
+ expect(last_response.body).to eq('simple2')
700
+ end
701
+ end
702
+ end
703
+
704
+ context 'an app that groups routes together in scopes' do
705
+ let :app do
706
+ App.new do
707
+ route 'scoped' do
708
+ before do |_, response|
709
+ response.headers['CommonBefore'] = 'yes'
710
+ end
711
+
712
+ after do |_, response|
713
+ response.headers['CommonAfter'] = 'yes'
714
+ end
715
+
716
+ scope do
717
+ before do |_, response|
718
+ response.headers['BeforeScope'] = '1'
719
+ end
720
+
721
+ after do |_, response|
722
+ response.headers['AfterScope'] = '1'
723
+ end
724
+
725
+ get do
726
+ 'scope1-level1'
727
+ end
728
+
729
+ route '1' do
730
+ get do
731
+ 'scope1-level2'
732
+ end
733
+ end
734
+ end
735
+
736
+ scope do
737
+ before do |_, response|
738
+ response.headers['BeforeScope'] = '2'
739
+ end
740
+
741
+ after do |_, response|
742
+ response.headers['AfterScope'] = '2'
743
+ end
744
+
745
+ get do
746
+ 'scope2-level1'
747
+ end
748
+
749
+ scope do
750
+ before do |_, response|
751
+ response.headers['BeforeSubScope'] = '2'
752
+ end
753
+
754
+ route '2' do
755
+ get do
756
+ 'scope2-level2'
757
+ end
758
+ end
759
+ end
760
+ end
761
+ end
762
+ end
763
+ end
764
+
765
+ it 'routes a request' do
766
+ get '/scoped/1'
767
+ expect(last_response.status).to eq(200)
768
+ expect(last_response.body).to eq('scope1-level2')
769
+ get '/scoped/2'
770
+ expect(last_response.status).to eq(200)
771
+ expect(last_response.body).to eq('scope2-level2')
772
+ end
773
+
774
+ it 'picks the handler from the last scope when there are multiple candidates', pending: true do
775
+ get '/scoped'
776
+ expect(last_response.status).to eq(200)
777
+ expect(last_response.body).to eq('scope2-level1')
778
+ end
779
+
780
+ it 'calls the route\'s parent scope\'s before blocks only' do
781
+ get '/scoped/1'
782
+ expect(last_response.status).to eq(200)
783
+ expect(last_response.headers).to include('BeforeScope' => '1')
784
+ get '/scoped/2'
785
+ expect(last_response.status).to eq(200)
786
+ expect(last_response.headers).to include('BeforeScope' => '2', 'BeforeSubScope' => '2')
787
+ end
788
+
789
+ it 'calls the route\'s parent scope\'s after blocks only' do
790
+ get '/scoped/1'
791
+ expect(last_response.status).to eq(200)
792
+ expect(last_response.headers).to include('AfterScope' => '1')
793
+ get '/scoped/2'
794
+ expect(last_response.status).to eq(200)
795
+ expect(last_response.headers).to include('AfterScope' => '2')
796
+ end
797
+
798
+ it 'calls the common before and after blocks' do
799
+ get '/scoped/1'
800
+ expect(last_response.status).to eq(200)
801
+ expect(last_response.headers).to include('CommonBefore' => 'yes', 'CommonAfter' => 'yes')
802
+ get '/scoped/2'
803
+ expect(last_response.status).to eq(200)
804
+ expect(last_response.headers).to include('CommonBefore' => 'yes', 'CommonAfter' => 'yes')
805
+ end
419
806
  end
420
807
 
421
808
  context 'an app that supports all HTTP methods' do
422
809
  let :app do
423
810
  App.new do
424
811
  get do |request|
425
- request.request_method
812
+ request.env['REQUEST_METHOD']
426
813
  end
427
814
 
428
815
  head do |request|
429
- request.request_method
816
+ request.env['REQUEST_METHOD']
430
817
  end
431
818
 
432
819
  options do |request|
433
- request.request_method
820
+ request.env['REQUEST_METHOD']
434
821
  end
435
822
 
436
823
  delete do |request|
437
- request.request_method
824
+ request.env['REQUEST_METHOD']
438
825
  end
439
826
 
440
827
  post do |request|
441
- request.request_method
828
+ request.env['REQUEST_METHOD']
442
829
  end
443
830
 
444
831
  put do |request|
445
- request.request_method
832
+ request.env['REQUEST_METHOD']
446
833
  end
447
834
 
448
835
  patch do |request|
449
- request.request_method
836
+ request.env['REQUEST_METHOD']
450
837
  end
451
838
 
452
839
  route 'anything' do
453
840
  any do |request|
454
- request.request_method
841
+ request.env['REQUEST_METHOD']
455
842
  end
456
843
  end
457
844
  end
@@ -522,6 +909,10 @@ module Regal
522
909
  'top_level_helper'
523
910
  end
524
911
 
912
+ rescue_from StandardError do |_, _, response|
913
+ response.body = top_level_helper
914
+ end
915
+
525
916
  route 'one' do
526
917
  def first_level_helper
527
918
  'first_level_helper'
@@ -549,6 +940,12 @@ module Regal
549
940
  end
550
941
  end
551
942
  end
943
+
944
+ route 'boom' do
945
+ get do
946
+ raise 'Bork'
947
+ end
948
+ end
552
949
  end
553
950
  end
554
951
 
@@ -575,164 +972,80 @@ module Regal
575
972
  expect(last_response.status).to eq(200)
576
973
  expect(last_response.body).to include('after:top_level_helper,first_level_helper,second_level_helper')
577
974
  end
975
+
976
+ it 'can use the helper methods in rescue blocks' do
977
+ get '/boom'
978
+ expect(last_response.status).to eq(200)
979
+ expect(last_response.body).to include('top_level_helper')
980
+ end
578
981
  end
579
982
 
580
983
  context 'an app that receives configuration when created' do
581
984
  let :app do
582
- App.new(this_thing, that_other_thing) do
583
- setup do |*args|
584
- @args = args
985
+ App.new(fuzzinator: fuzzinator, blip_count: 3, counter: 0, str: '') do
986
+ route 'blip' do
987
+ get do |request|
988
+ "blip\n" * request.attributes[:blip_count]
989
+ end
585
990
  end
586
991
 
587
- get do
588
- @args.join(',')
992
+ route 'fuzz' do
993
+ get do |request|
994
+ request.attributes[:fuzzinator].fuzz(request.parameters['s'])
995
+ end
589
996
  end
590
997
 
591
- route 'one' do
592
- setup do |thing1, thing2|
593
- @thing1 = thing1
594
- @thing2 = thing2
998
+ route 'increment' do
999
+ before do |request|
1000
+ request.attributes[:counter] += 1
595
1001
  end
596
1002
 
597
- get do
598
- [*@args, @thing1, @thing2].join(',')
1003
+ get do |request|
1004
+ request.attributes[:counter].to_s
599
1005
  end
600
1006
  end
601
1007
 
602
- route 'two' do
603
- setup do |thing1, thing2|
604
- @thing1 = thing1
605
- @thing2 = thing2
606
- end
607
-
608
- setup do |_, thing_two|
609
- @thing_two = thing_two
1008
+ route 'append' do
1009
+ before do |request|
1010
+ request.attributes[:str] << '1'
610
1011
  end
611
1012
 
612
- get do
613
- [*@args, @thing1, @thing2, @thing_two].join(',')
1013
+ get do |request|
1014
+ request.attributes[:str].to_s
614
1015
  end
615
1016
  end
616
1017
  end
617
1018
  end
618
1019
 
619
- let :this_thing do
620
- double(:this_thing, to_s: 'this_thing')
1020
+ let :fuzzinator do
1021
+ double(:fuzzinator)
621
1022
  end
622
1023
 
623
- let :that_other_thing do
624
- double(:that_other_thing, to_s: 'that_other_thing')
1024
+ before do
1025
+ allow(fuzzinator).to receive(:fuzz) { |s| s.split('').join('z') }
625
1026
  end
626
1027
 
627
- it 'calls its setup methods with the configuration' do
628
- get '/'
629
- expect(last_response.status).to eq(200)
630
- expect(last_response.body).to eq('this_thing,that_other_thing')
631
- end
632
-
633
- it 'calls the setup methods of all routes' do
634
- get '/one'
1028
+ it 'can access the hash given to .new through the request attributes hash' do
1029
+ get '/blip'
635
1030
  expect(last_response.status).to eq(200)
636
- expect(last_response.body).to eq('this_thing,that_other_thing,this_thing,that_other_thing')
637
- end
638
-
639
- it 'calls all setup methods' do
640
- get '/two'
1031
+ expect(last_response.body).to eq("blip\nblip\nblip\n")
1032
+ get '/fuzz?s=badaboom'
641
1033
  expect(last_response.status).to eq(200)
642
- expect(last_response.body).to eq('this_thing,that_other_thing,this_thing,that_other_thing,that_other_thing')
643
- end
644
- end
645
-
646
- context 'an app that uses Rack middleware' do
647
- class Reverser
648
- def initialize(app)
649
- @app = app
650
- end
651
-
652
- def call(env)
653
- response = @app.call(env)
654
- body = response[2][0]
655
- body && body.reverse!
656
- response
657
- end
658
- end
659
-
660
- class Uppercaser
661
- def initialize(app)
662
- @app = app
663
- end
664
-
665
- def call(env)
666
- response = @app.call(env)
667
- body = response[2][0]
668
- body && body.upcase!
669
- response
670
- end
671
- end
672
-
673
- class Mutator
674
- def initialize(app, &block)
675
- @app = app
676
- @block = block
677
- end
678
-
679
- def call(env)
680
- @app.call(@block.call(env))
681
- end
1034
+ expect(last_response.body).to eq('bzazdzazbzozozm')
682
1035
  end
683
1036
 
684
- let :app do
685
- App.new do
686
- use Reverser
687
-
688
- get do
689
- 'lorem ipsum'
690
- end
691
-
692
- route 'more' do
693
- use Uppercaser
694
-
695
- get do
696
- 'dolor sit'
697
- end
698
- end
699
-
700
- route 'hello' do
701
- use Rack::Runtime, 'Regal'
702
- use Mutator do |env|
703
- env['app.greeting'] = 'Bonjour'
704
- env
705
- end
706
-
707
- get do |request|
708
- request.env['app.greeting'] + ', ' + request.parameters['name']
709
- end
710
- end
711
- end
712
- end
713
-
714
- it 'calls the middleware when processing the request' do
715
- get '/'
716
- expect(last_response.status).to eq(200)
717
- expect(last_response.body).to eq('muspi merol')
718
- end
719
-
720
- it 'calls the middleware of all routes' do
721
- get '/more'
722
- expect(last_response.status).to eq(200)
723
- expect(last_response.body).to eq('TIS ROLOD')
724
- end
725
-
726
- it 'passes arguments when instantiating the middleware' do
727
- get '/hello?name=Eve'
728
- expect(last_response.status).to eq(200)
729
- expect(last_response.headers).to have_key('X-Runtime-Regal')
1037
+ it 'gives each request its own copy of the attributes' do
1038
+ get '/increment'
1039
+ get '/increment'
1040
+ get '/increment'
1041
+ expect(last_response.body).to eq('1')
730
1042
  end
731
1043
 
732
- it 'passes blocks when instantiating the middleware' do
733
- get '/hello?name=Eve'
734
- expect(last_response.status).to eq(200)
735
- expect(last_response.body).to eq('Bonjour, Eve'.reverse)
1044
+ it 'does not make a deep copy of the attributes' do
1045
+ get '/append'
1046
+ get '/append'
1047
+ get '/append'
1048
+ expect(last_response.body).to eq('111')
736
1049
  end
737
1050
  end
738
1051
 
@@ -761,6 +1074,11 @@ module Regal
761
1074
  'I will not be used'
762
1075
  end
763
1076
  end
1077
+
1078
+ route 'nil-body' do
1079
+ get do
1080
+ end
1081
+ end
764
1082
  end
765
1083
  end
766
1084
 
@@ -778,6 +1096,11 @@ module Regal
778
1096
  get '/no-body'
779
1097
  expect(last_response.body).to be_empty
780
1098
  end
1099
+
1100
+ it 'assumes that a nil body is no body' do
1101
+ get '/nil-body'
1102
+ expect(last_response.body).to be_empty
1103
+ end
781
1104
  end
782
1105
 
783
1106
  context 'an app that responds with no-body response codes' do
@@ -791,6 +1114,17 @@ module Regal
791
1114
  end
792
1115
  end
793
1116
  end
1117
+
1118
+ route 'with-after' do
1119
+ get do |_, response|
1120
+ response.status = 204
1121
+ 'this will not be returned'
1122
+ end
1123
+
1124
+ after do |_, response|
1125
+ response.body = 'this will not be returned either'
1126
+ end
1127
+ end
794
1128
  end
795
1129
  end
796
1130
 
@@ -801,6 +1135,12 @@ module Regal
801
1135
  expect(last_response.body).to be_empty
802
1136
  end
803
1137
  end
1138
+
1139
+ it 'ignores response bodies set by after blocks' do
1140
+ get '/with-after'
1141
+ expect(last_response.status).to eq(204)
1142
+ expect(last_response.body).to be_empty
1143
+ end
804
1144
  end
805
1145
 
806
1146
  context 'an app that raises exceptions' do
@@ -808,6 +1148,45 @@ module Regal
808
1148
  class AppError < StandardError; end
809
1149
  class SpecificError < AppError; end
810
1150
 
1151
+ MountedRescuingApp = App.create do
1152
+ rescue_from SpecificError do |_, _, response|
1153
+ response.body = 'handled SpecificError in the mounted app'
1154
+ end
1155
+
1156
+ route 'raise-specific-error' do
1157
+ get do
1158
+ raise SpecificError, 'Blam!'
1159
+ end
1160
+ end
1161
+
1162
+ route 'raise-app-error' do
1163
+ get do
1164
+ raise AppError, 'Kaboom!'
1165
+ end
1166
+ end
1167
+
1168
+ route 'raise-and-and-handle-app-error' do
1169
+ rescue_from AppError do |_, _, response|
1170
+ response.body = 'handled AppError in the mounted app'
1171
+ end
1172
+
1173
+ get do
1174
+ raise AppError, 'Kaboom!'
1175
+ end
1176
+ end
1177
+ end
1178
+
1179
+ MountedNonRescuingApp = App.create do
1180
+ after do
1181
+ raise AppError, 'Kaboom!'
1182
+ end
1183
+
1184
+ route 'raise-from-after' do
1185
+ get do
1186
+ end
1187
+ end
1188
+ end
1189
+
811
1190
  let :app do
812
1191
  App.new do
813
1192
  route 'unhandled' do
@@ -851,15 +1230,25 @@ module Regal
851
1230
  end
852
1231
 
853
1232
  route 'from-after' do
1233
+ after do |_, response|
1234
+ response.headers['NextAfterWasCalled'] = 'yes'
1235
+ end
1236
+
854
1237
  after do
855
1238
  raise SpecificError, 'Kazam!'
856
1239
  end
857
1240
 
858
- after do |_, response|
859
- response.headers['NextAfterWasCalled'] = 'yes'
1241
+ get do
1242
+ end
1243
+ end
1244
+
1245
+ route 'from-rescue' do
1246
+ rescue_from SpecificError do |e|
1247
+ raise AppError, 'Badaboom!'
860
1248
  end
861
1249
 
862
1250
  get do
1251
+ raise SpecificError, 'Kaboom!'
863
1252
  end
864
1253
  end
865
1254
 
@@ -871,6 +1260,40 @@ module Regal
871
1260
  raise SpecificError, 'Bam!'
872
1261
  end
873
1262
  end
1263
+
1264
+ route 'at-the-right-level' do
1265
+ rescue_from SpecificError do |_, _, response|
1266
+ response.headers['HandledAtLevel'] = '2'
1267
+ end
1268
+
1269
+ route 'level-3' do
1270
+ before do
1271
+ raise SpecificError, 'Badam!'
1272
+ end
1273
+
1274
+ rescue_from SpecificError do |_, _, response|
1275
+ response.headers['HandledAtLevel'] = '3'
1276
+ end
1277
+
1278
+ route 'level-4' do
1279
+ rescue_from SpecificError do |_, _, response|
1280
+ response.headers['HandledAtLevel'] = '4'
1281
+ end
1282
+
1283
+ get do
1284
+ end
1285
+ end
1286
+ end
1287
+ end
1288
+ end
1289
+
1290
+ route 'with-mounted-app' do
1291
+ rescue_from AppError do |_, _, response|
1292
+ response.body = 'handled AppError in the mounting app'
1293
+ end
1294
+
1295
+ mount MountedRescuingApp
1296
+ mount MountedNonRescuingApp
874
1297
  end
875
1298
  end
876
1299
  end
@@ -901,10 +1324,16 @@ module Regal
901
1324
  expect(last_response.body).to eq('Bang!')
902
1325
  end
903
1326
 
1327
+ it 'delegates them to matching error handlers at the same level, not below' do
1328
+ get '/handled/at-the-right-level/level-3/level-4'
1329
+ expect(last_response.headers).to include('HandledAtLevel' => '3')
1330
+ end
1331
+
904
1332
  it 'calls after blocks when errors are handled' do
905
1333
  get '/handled/from-before'
906
1334
  expect(last_response.headers['WasAfterCalled']).to eq('yes')
907
1335
  end
1336
+
908
1337
  end
909
1338
 
910
1339
  context 'from after blocks' do
@@ -919,6 +1348,37 @@ module Regal
919
1348
  expect(last_response.headers['WasAfterCalled']).to eq('yes')
920
1349
  end
921
1350
  end
1351
+
1352
+ context 'from rescue blocks' do
1353
+ it 'delegates them to the next matching error handler' do
1354
+ get '/handled/from-rescue'
1355
+ expect(last_response.body).to eq('Badaboom!')
1356
+ end
1357
+ end
1358
+
1359
+ context 'from mounted apps' do
1360
+ it 'delegates them to matching error handlers in the mounting app' do
1361
+ get '/with-mounted-app/raise-app-error'
1362
+ expect(last_response.body).to eq('handled AppError in the mounting app')
1363
+ end
1364
+
1365
+ it 'delegates them to matching error handlers declared at the top of the mounted app' do
1366
+ get '/with-mounted-app/raise-specific-error'
1367
+ expect(last_response.body).to eq('handled SpecificError in the mounted app')
1368
+ end
1369
+
1370
+ it 'delegates them to matching error handlers declared in routes of the mounted app' do
1371
+ get '/with-mounted-app/raise-and-and-handle-app-error'
1372
+ expect(last_response.body).to eq('handled AppError in the mounted app')
1373
+ end
1374
+
1375
+ context 'in after blocks' do
1376
+ it 'delegates them to a matching error handler' do
1377
+ get '/with-mounted-app/raise-from-after'
1378
+ expect(last_response.body).to eq('handled AppError in the mounting app')
1379
+ end
1380
+ end
1381
+ end
922
1382
  end
923
1383
  end
924
1384
  end