Add option to break a chain if error occurs
[catagits/Catalyst-Runtime.git] / t / aggregate / live_component_controller_action_chained.t
1 #!perl
2
3 use strict;
4 use warnings;
5
6 use FindBin;
7 use lib "$FindBin::Bin/../lib";
8
9 our $iters;
10
11 BEGIN { $iters = $ENV{CAT_BENCH_ITERS} || 1; }
12
13 use Test::More;
14 use URI;
15 use URI::QueryParam;
16 use Catalyst::Test 'TestApp';
17
18 if ( $ENV{CAT_BENCHMARK} ) {
19     require Benchmark;
20     Benchmark::timethis( $iters, \&run_tests );
21 }
22 else {
23     for ( 1 .. $iters ) {
24         run_tests($_);
25     }
26 }
27
28 sub run_tests {
29     my ($run_number) = @_;
30
31     #
32     #   This is a simple test where the parent and child actions are
33     #   within the same controller.
34     #
35     {
36         my @expected = qw[
37           TestApp::Controller::Action::Chained->begin
38           TestApp::Controller::Action::Chained->foo
39           TestApp::Controller::Action::Chained->endpoint
40           TestApp::Controller::Action::Chained->end
41         ];
42
43         my $expected = join( ", ", @expected );
44
45         ok( my $response = request('http://localhost/chained/foo/1/end/2'), 'chained + local endpoint' );
46         is( $response->header('X-Catalyst-Executed'),
47             $expected, 'Executed actions' );
48         is( $response->content, '1; 2', 'Content OK' );
49     }
50
51     #
52     #   This makes sure the above isn't found if the argument for the
53     #   end action isn't supplied.
54     #
55     {
56         my $expected = undef;
57
58         ok( my $response = request('http://localhost/chained/foo/1/end'), 
59             'chained + local endpoint; missing last argument' );
60         is( $response->header('X-Catalyst-Executed'),
61             $expected, 'Executed actions' );
62         is( $response->code, 500, 'Status OK' );
63     }
64
65     #
66     #   Tests the case when the child action is placed in a subcontroller.
67     #
68     {
69         my @expected = qw[
70           TestApp::Controller::Action::Chained->begin
71           TestApp::Controller::Action::Chained->foo
72           TestApp::Controller::Action::Chained::Foo->spoon
73           TestApp::Controller::Action::Chained->end
74         ];
75
76         my $expected = join( ", ", @expected );
77
78         ok( my $response = request('http://localhost/chained/foo/1/spoon'), 'chained + subcontroller endpoint' );
79         is( $response->header('X-Catalyst-Executed'),
80             $expected, 'Executed actions' );
81         is( $response->content, '1; ', 'Content OK' );
82     }
83
84     #
85     #   Tests if the relative specification (e.g.: Chained('bar') ) works
86     #   as expected.
87     #
88     {
89         my @expected = qw[
90           TestApp::Controller::Action::Chained->begin
91           TestApp::Controller::Action::Chained->bar
92           TestApp::Controller::Action::Chained->finale
93           TestApp::Controller::Action::Chained->end
94         ];
95
96         my $expected = join( ", ", @expected );
97
98         ok( my $response = request('http://localhost/chained/bar/1/spoon'), 'chained + relative endpoint' );
99         is( $response->header('X-Catalyst-Executed'),
100             $expected, 'Executed actions' );
101         is( $response->content, '; 1, spoon', 'Content OK' );
102     }
103
104     #
105     #   Just a test for multiple arguments.
106     #
107     {
108         my @expected = qw[
109           TestApp::Controller::Action::Chained->begin
110           TestApp::Controller::Action::Chained->foo2
111           TestApp::Controller::Action::Chained->endpoint2
112           TestApp::Controller::Action::Chained->end
113         ];
114
115         my $expected = join( ", ", @expected );
116
117         ok( my $response = request('http://localhost/chained/foo2/10/20/end2/15/25'), 
118             'chained + local (2 args each)' );
119         is( $response->header('X-Catalyst-Executed'),
120             $expected, 'Executed actions' );
121         is( $response->content, '10, 20; 15, 25', 'Content OK' );
122     }
123
124     #
125     #   The first three-chain test tries to call the action with :Args(1)
126     #   specification. There's also a one action with a :CaptureArgs(1)
127     #   attribute, that should not be dispatched to.
128     #
129     {
130         my @expected = qw[
131           TestApp::Controller::Action::Chained->begin
132           TestApp::Controller::Action::Chained->one_end
133           TestApp::Controller::Action::Chained->end
134         ];
135
136         my $expected = join( ", ", @expected );
137
138         ok( my $response = request('http://localhost/chained/one/23'),
139             'three-chain (only first)' );
140         is( $response->header('X-Catalyst-Executed'),
141             $expected, 'Executed actions' );
142         is( $response->content, '; 23', 'Content OK' );
143     }
144
145     #
146     #   This is the second three-chain test, it goes for the action that
147     #   handles "/one/$cap/two/$arg1/$arg2" paths. Should be the two action
148     #   having :Args(2), not the one having :CaptureArgs(2).
149     #
150     {
151         my @expected = qw[
152           TestApp::Controller::Action::Chained->begin
153           TestApp::Controller::Action::Chained->one
154           TestApp::Controller::Action::Chained->two_end
155           TestApp::Controller::Action::Chained->end
156         ];
157
158         my $expected = join( ", ", @expected );
159
160         ok( my $response = request('http://localhost/chained/one/23/two/23/46'),
161             'three-chain (up to second)' );
162         is( $response->header('X-Catalyst-Executed'),
163             $expected, 'Executed actions' );
164         is( $response->content, '23; 23, 46', 'Content OK' );
165     }
166
167     #
168     #   Last of the three-chain tests. Has no concurrent action with :CaptureArgs
169     #   and is more thought to simply test the chain as a whole and the 'two'
170     #   action specifying :CaptureArgs.
171     #
172     {
173         my @expected = qw[
174           TestApp::Controller::Action::Chained->begin
175           TestApp::Controller::Action::Chained->one
176           TestApp::Controller::Action::Chained->two
177           TestApp::Controller::Action::Chained->three_end
178           TestApp::Controller::Action::Chained->end
179         ];
180
181         my $expected = join( ", ", @expected );
182
183         ok( my $response = request('http://localhost/chained/one/23/two/23/46/three/1/2/3'),
184             'three-chain (all three)' );
185         is( $response->header('X-Catalyst-Executed'),
186             $expected, 'Executed actions' );
187         is( $response->content, '23, 23, 46; 1, 2, 3', 'Content OK' );
188     }
189
190     #
191     #   Tests dispatching on number of arguments for :Args. This should be
192     #   dispatched to the action expecting one argument.
193     #
194     {
195         my @expected = qw[
196           TestApp::Controller::Action::Chained->begin
197           TestApp::Controller::Action::Chained->multi1
198           TestApp::Controller::Action::Chained->end
199         ];
200
201         my $expected = join( ", ", @expected );
202
203         ok( my $response = request('http://localhost/chained/multi/23'),
204             'multi-action (one arg)' );
205         is( $response->header('X-Catalyst-Executed'),
206             $expected, 'Executed actions' );
207         is( $response->content, '; 23', 'Content OK' );
208     }
209
210     #
211     #   Belongs to the former test and goes for the action expecting two arguments.
212     #
213     {
214         my @expected = qw[
215           TestApp::Controller::Action::Chained->begin
216           TestApp::Controller::Action::Chained->multi2
217           TestApp::Controller::Action::Chained->end
218         ];
219
220         my $expected = join( ", ", @expected );
221
222         ok( my $response = request('http://localhost/chained/multi/23/46'),
223             'multi-action (two args)' );
224         is( $response->header('X-Catalyst-Executed'),
225             $expected, 'Executed actions' );
226         is( $response->content, '; 23, 46', 'Content OK' );
227     }
228
229     #
230     #   Dispatching on argument count again, this time we provide too many
231     #   arguments, so dispatching should fail.
232     #
233     {
234         my $expected = undef;
235
236         ok( my $response = request('http://localhost/chained/multi/23/46/67'),
237             'multi-action (three args, should lead to error)' );
238         is( $response->header('X-Catalyst-Executed'),
239             $expected, 'Executed actions' );
240         is( $response->code, 500, 'Status OK' );
241     }
242
243     #
244     #   This tests the case when an action says it's the child of an action in
245     #   a subcontroller.
246     #
247     {
248         my @expected = qw[
249           TestApp::Controller::Action::Chained->begin
250           TestApp::Controller::Action::Chained::Foo->higher_root
251           TestApp::Controller::Action::Chained->higher_root
252           TestApp::Controller::Action::Chained->end
253         ];
254
255         my $expected = join( ", ", @expected );
256
257         ok( my $response = request('http://localhost/chained/higher_root/23/bar/11'),
258             'root higher than child' );
259         is( $response->header('X-Catalyst-Executed'),
260             $expected, 'Executed actions' );
261         is( $response->content, '23; 11', 'Content OK' );
262     }
263
264     #
265     #   Just a more complex version of the former test. It tests if a controller ->
266     #   subcontroller -> controller dispatch works.
267     #
268     {
269         my @expected = qw[
270           TestApp::Controller::Action::Chained->begin
271           TestApp::Controller::Action::Chained->pcp1
272           TestApp::Controller::Action::Chained::Foo->pcp2
273           TestApp::Controller::Action::Chained->pcp3
274           TestApp::Controller::Action::Chained->end
275         ];
276
277         my $expected = join( ", ", @expected );
278
279         ok( my $response = request('http://localhost/chained/pcp1/1/pcp2/2/pcp3/3'),
280             'parent -> child -> parent' );
281         is( $response->header('X-Catalyst-Executed'),
282             $expected, 'Executed actions' );
283         is( $response->content, '1, 2; 3', 'Content OK' );
284     }
285
286     #
287     #   Tests dispatch on capture number. This test is for a one capture action.
288     #
289     {
290         my @expected = qw[
291           TestApp::Controller::Action::Chained->begin
292           TestApp::Controller::Action::Chained->multi_cap1
293           TestApp::Controller::Action::Chained->multi_cap_end1
294           TestApp::Controller::Action::Chained->end
295         ];
296
297         my $expected = join( ", ", @expected );
298
299         ok( my $response = request('http://localhost/chained/multi_cap/1/baz'),
300             'dispatch on capture num 1' );
301         is( $response->header('X-Catalyst-Executed'),
302             $expected, 'Executed actions' );
303         is( $response->content, '1; ', 'Content OK' );
304     }
305
306     #
307     #   Belongs to the former test. This one goes for the action expecting two
308     #   captures.
309     #
310     {
311         my @expected = qw[
312           TestApp::Controller::Action::Chained->begin
313           TestApp::Controller::Action::Chained->multi_cap2
314           TestApp::Controller::Action::Chained->multi_cap_end2
315           TestApp::Controller::Action::Chained->end
316         ];
317
318         my $expected = join( ", ", @expected );
319
320         ok( my $response = request('http://localhost/chained/multi_cap/1/2/baz'),
321             'dispatch on capture num 2' );
322         is( $response->header('X-Catalyst-Executed'),
323             $expected, 'Executed actions' );
324         is( $response->content, '1, 2; ', 'Content OK' );
325     }
326
327     #
328     #   Tests the priority of a slurpy arguments action (with :Args) against
329     #   two actions chained together. The two actions should win.
330     #
331     {
332         my @expected = qw[
333           TestApp::Controller::Action::Chained->begin
334           TestApp::Controller::Action::Chained->priority_a2
335           TestApp::Controller::Action::Chained->priority_a2_end
336           TestApp::Controller::Action::Chained->end
337         ];
338
339         my $expected = join( ", ", @expected );
340
341         ok( my $response = request('http://localhost/chained/priority_a/1/end/2'),
342             'priority - slurpy args vs. parent/child' );
343         is( $response->header('X-Catalyst-Executed'),
344             $expected, 'Executed actions' );
345         is( $response->content, '1; 2', 'Content OK' );
346     }
347
348     #
349     #   This belongs to the former test but tests if two chained actions have
350     #   priority over an action with the exact arguments.
351     #
352     {
353         my @expected = qw[
354           TestApp::Controller::Action::Chained->begin
355           TestApp::Controller::Action::Chained->priority_b2
356           TestApp::Controller::Action::Chained->priority_b2_end
357           TestApp::Controller::Action::Chained->end
358         ];
359
360         my $expected = join( ", ", @expected );
361
362         ok( my $response = request('http://localhost/chained/priority_b/1/end/2'),
363             'priority - fixed args vs. parent/child' );
364         is( $response->header('X-Catalyst-Executed'),
365             $expected, 'Executed actions' );
366         is( $response->content, '1; 2', 'Content OK' );
367     }
368
369     #
370     #   This belongs to the former test but tests if two chained actions have
371     #   priority over an action with one child action not having the Args() attr set.
372     #
373     {
374         my @expected = qw[
375           TestApp::Controller::Action::Chained->begin
376           TestApp::Controller::Action::Chained->priority_c1
377           TestApp::Controller::Action::Chained->priority_c2_xyz
378           TestApp::Controller::Action::Chained->end
379         ];
380
381         my $expected = join( ", ", @expected );
382
383         ok( my $response = request('http://localhost/chained/priority_c/1/xyz/'),
384             'priority - no Args() order mismatch' );
385         is( $response->header('X-Catalyst-Executed'),
386             $expected, 'Executed actions' );
387         is( $response->content, '1; ', 'Content OK' );
388     }
389
390     #
391     #   Test dispatching between two controllers that are on the same level and
392     #   therefor have no parent/child relationship.
393     #
394     {
395         my @expected = qw[
396           TestApp::Controller::Action::Chained->begin
397           TestApp::Controller::Action::Chained::Bar->cross1
398           TestApp::Controller::Action::Chained::Foo->cross2
399           TestApp::Controller::Action::Chained->end
400         ];
401
402         my $expected = join( ", ", @expected );
403
404         ok( my $response = request('http://localhost/chained/cross/1/end/2'),
405             'cross controller w/o par/child relation' );
406         is( $response->header('X-Catalyst-Executed'),
407             $expected, 'Executed actions' );
408         is( $response->content, '1; 2', 'Content OK' );
409     }
410
411     #
412     #   This is for testing if the arguments got passed to the actions 
413     #   correctly.
414     #
415     {
416         my @expected = qw[
417           TestApp::Controller::Action::Chained->begin
418           TestApp::Controller::Action::Chained::PassedArgs->first
419           TestApp::Controller::Action::Chained::PassedArgs->second
420           TestApp::Controller::Action::Chained::PassedArgs->third
421           TestApp::Controller::Action::Chained::PassedArgs->end
422         ];
423
424         my $expected = join( ", ", @expected );
425
426         ok( my $response = request('http://localhost/chained/passedargs/a/1/b/2/c/3'),
427             'Correct arguments passed to actions' );
428         is( $response->header('X-Catalyst-Executed'),
429             $expected, 'Executed actions' );
430         is( $response->content, '1; 2; 3', 'Content OK' );
431     }
432
433     #
434     #   The :Args attribute is optional, we check the action not specifying
435     #   it with these tests.
436     #
437     {
438         my @expected = qw[
439           TestApp::Controller::Action::Chained->begin
440           TestApp::Controller::Action::Chained->opt_args
441           TestApp::Controller::Action::Chained->end
442         ];
443
444         my $expected = join( ", ", @expected );
445
446         ok( my $response = request('http://localhost/chained/opt_args/1/2/3'),
447             'Optional :Args attribute working' );
448         is( $response->header('X-Catalyst-Executed'),
449             $expected, 'Executed actions' );
450         is( $response->content, '; 1, 2, 3', 'Content OK' );
451     }
452
453     #
454     #   Tests for optional PathPart attribute.
455     #
456     {
457         my @expected = qw[
458           TestApp::Controller::Action::Chained->begin
459           TestApp::Controller::Action::Chained->opt_pp_start
460           TestApp::Controller::Action::Chained->opt_pathpart
461           TestApp::Controller::Action::Chained->end
462         ];
463
464         my $expected = join( ", ", @expected );
465
466         ok( my $response = request('http://localhost/chained/optpp/1/opt_pathpart/2'),
467             'Optional :PathName attribute working' );
468         is( $response->header('X-Catalyst-Executed'),
469             $expected, 'Executed actions' );
470         is( $response->content, '1; 2', 'Content OK' );
471     }
472
473     #
474     #   Tests for optional PathPart *and* Args attributes.
475     #
476     {
477         my @expected = qw[
478           TestApp::Controller::Action::Chained->begin
479           TestApp::Controller::Action::Chained->opt_all_start
480           TestApp::Controller::Action::Chained->oa
481           TestApp::Controller::Action::Chained->end
482         ];
483
484         my $expected = join( ", ", @expected );
485
486         ok( my $response = request('http://localhost/chained/optall/1/oa/2/3'),
487             'Optional :PathName *and* :Args attributes working' );
488         is( $response->header('X-Catalyst-Executed'),
489             $expected, 'Executed actions' );
490         is( $response->content, '1; 2, 3', 'Content OK' );
491     }
492
493     #
494     #   Test if :Chained is the same as :Chained('/')
495     #
496     {
497         my @expected = qw[
498           TestApp::Controller::Action::Chained->begin
499           TestApp::Controller::Action::Chained->rootdef
500           TestApp::Controller::Action::Chained->end
501         ];
502
503         my $expected = join( ", ", @expected );
504
505         ok( my $response = request('http://localhost/chained/rootdef/23'),
506             ":Chained is the same as :Chained('/')" );
507         is( $response->header('X-Catalyst-Executed'),
508             $expected, 'Executed actions' );
509         is( $response->content, '; 23', 'Content OK' );
510     }
511
512     #
513     #   Test if :Chained('.') is working
514     #
515     {
516         my @expected = qw[
517           TestApp::Controller::Action::Chained->begin
518           TestApp::Controller::Action::Chained->parentchain
519           TestApp::Controller::Action::Chained::ParentChain->child
520           TestApp::Controller::Action::Chained->end
521         ];
522
523         my $expected = join( ", ", @expected );
524
525         ok( my $response = request('http://localhost/chained/parentchain/1/child/2'),
526             ":Chained('.') chains to parent controller action" );
527         is( $response->header('X-Catalyst-Executed'),
528             $expected, 'Executed actions' );
529         is( $response->content, '1; 2', 'Content OK' );
530     }
531
532     #
533     #   Test if :Chained('../act') is working
534     #
535     {
536         my @expected = qw[
537           TestApp::Controller::Action::Chained->begin
538           TestApp::Controller::Action::Chained->one
539           TestApp::Controller::Action::Chained::ParentChain->chained_rel
540           TestApp::Controller::Action::Chained->end
541         ];
542
543         my $expected = join( ", ", @expected );
544
545         ok( my $response = request('http://localhost/chained/one/1/chained_rel/3/2'),
546             ":Chained('../action') chains to correct action" );
547         is( $response->header('X-Catalyst-Executed'),
548             $expected, 'Executed actions' );
549         is( $response->content, '1; 3, 2', 'Content OK' );
550     }
551
552     #
553     #   Test if ../ works to go up more than one level
554     #
555     {
556         my @expected = qw[
557             TestApp::Controller::Action::Chained->begin
558             TestApp::Controller::Action::Chained->one
559             TestApp::Controller::Action::Chained::ParentChain::Relative->chained_rel_two
560             TestApp::Controller::Action::Chained->end
561         ];
562
563         my $expected = join( ", ", @expected );
564
565         ok( my $response = request('http://localhost/chained/one/1/chained_rel_two/42/23'),
566             "../ works to go up more than one level" );
567         is( $response->header('X-Catalyst-Executed'),
568             $expected, 'Executed actions' );
569         is( $response->content, '1; 42, 23', 'Content OK' );
570     }
571
572     #
573     #   Test if :ChainedParent is working
574     #
575     {
576         my @expected = qw[
577           TestApp::Controller::Action::Chained->begin
578           TestApp::Controller::Action::Chained->loose
579           TestApp::Controller::Action::Chained::ParentChain->loose
580           TestApp::Controller::Action::Chained->end
581         ];
582
583         my $expected = join( ", ", @expected );
584
585         ok( my $response = request('http://localhost/chained/loose/4/loose/a/b'),
586             ":Chained('../action') chains to correct action" );
587         is( $response->header('X-Catalyst-Executed'),
588             $expected, 'Executed actions' );
589         is( $response->content, '4; a, b', 'Content OK' );
590     }
591
592     #
593     #   Test if :Chained('../name/act') is working
594     #
595     {
596         my @expected = qw[
597           TestApp::Controller::Action::Chained->begin
598           TestApp::Controller::Action::Chained::Bar->cross1
599           TestApp::Controller::Action::Chained::ParentChain->up_down
600           TestApp::Controller::Action::Chained->end
601         ];
602
603         my $expected = join( ", ", @expected );
604
605         ok( my $response = request('http://localhost/chained/cross/4/up_down/5'),
606             ":Chained('../action') chains to correct action" );
607         is( $response->header('X-Catalyst-Executed'),
608             $expected, 'Executed actions' );
609         is( $response->content, '4; 5', 'Content OK' );
610     }
611
612     #
613     #   Test behaviour of auto actions returning '1' for the chain.
614     #
615     {
616         my @expected = qw[
617           TestApp::Controller::Action::Chained->begin
618           TestApp::Controller::Action::Chained::Auto->auto
619           TestApp::Controller::Action::Chained::Auto::Foo->auto
620           TestApp::Controller::Action::Chained::Auto->foo
621           TestApp::Controller::Action::Chained::Auto::Foo->fooend
622           TestApp::Controller::Action::Chained->end
623         ];
624
625         my $expected = join( ", ", @expected );
626
627         ok( my $response = request('http://localhost/chained/autochain1/1/fooend/2'),
628             "Behaviour when auto returns 1 correct" );
629         is( $response->header('X-Catalyst-Executed'),
630             $expected, 'Executed actions' );
631         is( $response->content, '1; 2', 'Content OK' );
632     }
633
634     #
635     #   Test behaviour of auto actions returning '0' for the chain.
636     #
637     {
638         my @expected = qw[
639           TestApp::Controller::Action::Chained->begin
640           TestApp::Controller::Action::Chained::Auto->auto
641           TestApp::Controller::Action::Chained::Auto::Bar->auto
642           TestApp::Controller::Action::Chained->end
643         ];
644
645         my $expected = join( ", ", @expected );
646
647         ok( my $response = request('http://localhost/chained/autochain2/1/barend/2'),
648             "Behaviour when auto returns 0 correct" );
649         is( $response->header('X-Catalyst-Executed'),
650             $expected, 'Executed actions' );
651         is( $response->content, '1; 2', 'Content OK' );
652     }
653
654     #
655     #   Test what auto actions are run when namespaces are changed
656     #   horizontally.
657     #
658     {
659         my @expected = qw[
660           TestApp::Controller::Action::Chained->begin
661           TestApp::Controller::Action::Chained::Auto->auto
662           TestApp::Controller::Action::Chained::Auto::Foo->auto
663           TestApp::Controller::Action::Chained::Auto::Bar->crossloose
664           TestApp::Controller::Action::Chained::Auto::Foo->crossend
665           TestApp::Controller::Action::Chained->end
666         ];
667
668         my $expected = join( ", ", @expected );
669
670         ok( my $response = request('http://localhost/chained/auto_cross/1/crossend/2'),
671             "Correct auto actions are run on cross controller dispatch" );
672         is( $response->header('X-Catalyst-Executed'),
673             $expected, 'Executed actions' );
674         is( $response->content, '1; 2', 'Content OK' );
675     }
676
677     #
678     #   Test forwarding from auto action in chain dispatch.
679     #
680     {
681         my @expected = qw[
682           TestApp::Controller::Action::Chained->begin
683           TestApp::Controller::Action::Chained::Auto->auto
684           TestApp::Controller::Action::Chained::Auto::Forward->auto
685           TestApp::Controller::Action::Chained::Auto->fw3
686           TestApp::Controller::Action::Chained::Auto->fw1
687           TestApp::Controller::Action::Chained::Auto::Forward->forwardend
688           TestApp::Controller::Action::Chained->end
689         ];
690
691         my $expected = join( ", ", @expected );
692
693         ok( my $response = request('http://localhost/chained/auto_forward/1/forwardend/2'),
694             "Forwarding out of auto in chain" );
695         is( $response->header('X-Catalyst-Executed'),
696             $expected, 'Executed actions' );
697         is( $response->content, '1; 2', 'Content OK' );
698     }
699
700     #
701     #   Detaching out of the auto action of a chain.
702     #
703     {
704         my @expected = qw[
705           TestApp::Controller::Action::Chained->begin
706           TestApp::Controller::Action::Chained::Auto->auto
707           TestApp::Controller::Action::Chained::Auto::Detach->auto
708           TestApp::Controller::Action::Chained::Auto->fw3
709           TestApp::Controller::Action::Chained->end
710         ];
711
712         my $expected = join( ", ", @expected );
713
714         ok( my $response = request('http://localhost/chained/auto_detach/1/detachend/2'),
715             "Detaching out of auto in chain" );
716         is( $response->header('X-Catalyst-Executed'),
717             $expected, 'Executed actions' );
718         is( $response->content, '1; 2', 'Content OK' );
719     }
720
721     #
722     #   Test forwarding from auto action in chain dispatch.
723     #
724     {
725         my $expected = undef;
726
727         ok( my $response = request('http://localhost/chained/loose/23'),
728             "Loose end is not callable" );
729         is( $response->header('X-Catalyst-Executed'),
730             $expected, 'Executed actions' );
731         is( $response->code, 500, 'Status OK' );
732     }
733
734     #
735     #   Test forwarding out of a chain.
736     #
737     {
738         my @expected = qw[
739           TestApp::Controller::Action::Chained->begin
740           TestApp::Controller::Action::Chained->chain_fw_a
741           TestApp::Controller::Action::Chained->fw_dt_target
742           TestApp::Controller::Action::Chained->chain_fw_b
743           TestApp::Controller::Action::Chained->end
744         ];
745
746         my $expected = join( ", ", @expected );
747
748         ok( my $response = request('http://localhost/chained/chain_fw/1/end/2'),
749             "Forwarding out a chain" );
750         is( $response->header('X-Catalyst-Executed'),
751             $expected, 'Executed actions' );
752         is( $response->content, '1; 2', 'Content OK' );
753     }
754
755     #
756     #   Test detaching out of a chain.
757     #
758     {
759         my @expected = qw[
760           TestApp::Controller::Action::Chained->begin
761           TestApp::Controller::Action::Chained->chain_dt_a
762           TestApp::Controller::Action::Chained->fw_dt_target
763           TestApp::Controller::Action::Chained->end
764         ];
765
766         my $expected = join( ", ", @expected );
767
768         ok( my $response = request('http://localhost/chained/chain_dt/1/end/2'),
769             "Forwarding out a chain" );
770         is( $response->header('X-Catalyst-Executed'),
771             $expected, 'Executed actions' );
772         is( $response->content, '1; 2', 'Content OK' );
773     }
774
775     #
776     #
777     #
778     {
779         my @expected = qw[
780           TestApp::Controller::Action::Chained->begin
781           TestApp::Controller::Action::Chained->chain_die_a
782           TestApp::Controller::Action::Chained->end
783         ];
784
785         my $expected = join( ", ", @expected );
786
787         ok( my $response = request('http://localhost/chained/chain_die/1/end/2'),
788             "Break a chain in the middle" );
789         is( $response->header('X-Catalyst-Executed'),
790             $expected, 'Executed actions' );
791         is( $response->content, 'FATAL ERROR: break in the middle of a chain', 'Content OK' );
792     }
793
794     #
795     #   Tests that an uri_for to a chained root index action
796     #   returns the right value.
797     #
798     {
799         ok( my $response = request(
800             'http://localhost/action/chained/to_root' ),
801             'uri_for with chained root action as arg' );
802         like( $response->content,
803             qr(URI:https?://[^/]+/),
804             'Correct URI generated' );
805     }
806
807     #
808     #   Test interception of recursive chains. This test was added because at
809     #   one point during the :Chained development, Catalyst used to hang on
810     #   recursive chains.
811     #
812     {
813         eval { require 'TestAppChainedRecursive.pm' };
814         if ($run_number == 1) {
815             ok( ! $@, "Interception of recursive chains" );
816         }
817         else { pass( "Interception of recursive chains already tested" ) }
818     }
819
820     #
821     #   Test failure of absolute path part arguments.
822     #
823     {
824         eval { require 'TestAppChainedAbsolutePathPart.pm' };
825         if ($run_number == 1) {
826             like( $@, qr(foo/foo),
827                 "Usage of absolute path part argument emits error" );
828         }
829         else { pass( "Error on absolute path part arguments already tested" ) }
830     }
831
832     #
833     #   Test chained actions in the root controller
834     #
835     {
836         my @expected = qw[
837           TestApp::Controller::Action::Chained::Root->rootsub
838           TestApp::Controller::Action::Chained::Root->endpointsub
839           TestApp::Controller::Root->end
840         ];
841
842         my $expected = join( ", ", @expected );
843
844         ok( my $response = request('http://localhost/rootsub/1/endpointsub/2'), 'chained in root namespace' );
845         is( $response->header('X-Catalyst-Executed'),
846             $expected, 'Executed actions' );
847         is( $response->content, '', 'Content OK' );
848     }
849
850     #
851     #   Complex path with multiple empty pathparts
852     #
853     {
854         my @expected = qw[
855           TestApp::Controller::Action::Chained->begin
856           TestApp::Controller::Action::Chained->mult_nopp_base
857           TestApp::Controller::Action::Chained->mult_nopp_all
858           TestApp::Controller::Action::Chained->end
859         ];
860
861         my $expected = join( ", ", @expected );
862
863         ok( my $response = request('http://localhost/chained/mult_nopp'),
864             "Complex path with multiple empty pathparts" );
865         is( $response->header('X-Catalyst-Executed'),
866             $expected, 'Executed actions' );
867         is( $response->content, '; ', 'Content OK' );
868     }
869
870     #
871     #   Complex path with multiple non-capturing pathparts
872     # PathPart('') CaptureArgs(0), PathPart('foo') CaptureArgs(0), PathPart('') Args(0)
873     # should win over PathPart('') CaptureArgs(1), PathPart('') Args(0)
874     #
875     {
876         my @expected = qw[
877           TestApp::Controller::Action::Chained->begin
878           TestApp::Controller::Action::Chained->mult_nopp2_base
879           TestApp::Controller::Action::Chained->mult_nopp2_nocap
880           TestApp::Controller::Action::Chained->mult_nopp2_action
881           TestApp::Controller::Action::Chained->mult_nopp2_action_default
882           TestApp::Controller::Action::Chained->end
883         ];
884
885         my $expected = join( ", ", @expected );
886
887         ok( my $response = request('http://localhost/chained/mult_nopp2/action'),
888             "Complex path with multiple non-capturing pathparts" );
889         is( $response->header('X-Catalyst-Executed'),
890             $expected, 'Executed actions' );
891         is( $response->content, '; ', 'Content OK' );
892     }
893
894     #
895     #   Higher Args() hiding more specific CaptureArgs chains sections
896     #
897     {
898         my @expected = qw[
899             TestApp::Controller::Action::Chained->begin
900             TestApp::Controller::Action::Chained->cc_base
901             TestApp::Controller::Action::Chained->cc_link
902             TestApp::Controller::Action::Chained->cc_anchor
903             TestApp::Controller::Action::Chained->end
904             ];
905
906         my $expected = join ', ', @expected;
907
908         ok( my $response = request('http://localhost/chained/choose_capture/anchor.html'),
909             'Choose between an early Args() and a later more ideal chain' );
910         is( $response->header('X-Catalyst-Executed') => $expected, 'Executed actions');
911         is( $response->content => '; ', 'Content OK' );
912     }
913
914     #
915     #   Less specific chain not being seen correctly due to earlier looser capture
916     #
917     {
918         my @expected = qw[
919             TestApp::Controller::Action::Chained->begin
920             TestApp::Controller::Action::Chained->cc_base
921             TestApp::Controller::Action::Chained->cc_b
922             TestApp::Controller::Action::Chained->cc_b_link
923             TestApp::Controller::Action::Chained->cc_b_anchor
924             TestApp::Controller::Action::Chained->end
925             ];
926
927         my $expected = join ', ', @expected;
928
929         ok( my $response = request('http://localhost/chained/choose_capture/b/a/anchor.html'),
930             'Choose between a more specific chain and an earlier looser one' );
931         is( $response->header('X-Catalyst-Executed') => $expected, 'Executed actions');
932         is( $response->content => 'a; ', 'Content OK' );
933     }
934
935     #
936     #   Check we get the looser one when it's the correct match
937     #
938     {
939         my @expected = qw[
940             TestApp::Controller::Action::Chained->begin
941             TestApp::Controller::Action::Chained->cc_base
942             TestApp::Controller::Action::Chained->cc_a
943             TestApp::Controller::Action::Chained->cc_a_link
944             TestApp::Controller::Action::Chained->cc_a_anchor
945             TestApp::Controller::Action::Chained->end
946             ];
947
948         my $expected = join ', ', @expected;
949
950         ok( my $response = request('http://localhost/chained/choose_capture/a/a/anchor.html'),
951             'Choose between a more specific chain and an earlier looser one' );
952         is( $response->header('X-Catalyst-Executed') => $expected, 'Executed actions');
953         is( $response->content => 'a; anchor.html', 'Content OK' );
954     }
955
956     # CaptureArgs(1) PathPart('...') should win over CaptureArgs(2) PathPart('')
957     {
958         my @expected = qw[
959           TestApp::Controller::Action::Chained->begin
960           TestApp::Controller::Action::Chained::CaptureArgs->base
961           TestApp::Controller::Action::Chained::CaptureArgs->one_arg
962           TestApp::Controller::Action::Chained::CaptureArgs->edit_one_arg
963           TestApp::Controller::Action::Chained::CaptureArgs->end
964         ];
965
966         my $expected = join( ", ", @expected );
967
968         # should dispatch to /base/one_args/edit_one_arg
969         ok( my $response = request('http://localhost/captureargs/one/edit'),
970             'Correct arg order ran' );
971         TODO: {
972         local $TODO = 'Known bug';
973         is( $response->header('X-Catalyst-Executed'),
974             $expected, 'Executed actions' );
975         is( $response->content, 'base; one_arg; edit_one_arg', 'Content OK' );
976         }
977     }
978
979     #  PathPart('...') Args(1) should win over CaptureArgs(2) PathPart('')
980     {
981         my @expected = qw[
982           TestApp::Controller::Action::Chained->begin
983           TestApp::Controller::Action::Chained::CaptureArgs->base
984           TestApp::Controller::Action::Chained::CaptureArgs->test_one_arg
985           TestApp::Controller::Action::Chained::CaptureArgs->end
986         ];
987
988         my $expected = join( ", ", @expected );
989
990         # should dispatch to /base/test_one_arg
991         ok( my $response = request('http://localhost/captureargs/test/one'),
992             'Correct pathpart/arg ran' );
993         TODO: {
994         local $TODO = 'Known bug';
995         is( $response->header('X-Catalyst-Executed'),
996             $expected, 'Executed actions' );
997         is( $response->content, 'base; test_plus_arg; one;', 'Content OK' );
998         }
999     }
1000
1001     #
1002     #   Args(0) should win over Args() if we actually have no arguments.
1003     {
1004         my @expected = qw[
1005           TestApp::Controller::Action::Chained->begin
1006           TestApp::Controller::Action::Chained::ArgsOrder->base
1007           TestApp::Controller::Action::Chained::ArgsOrder->index
1008           TestApp::Controller::Action::Chained::ArgsOrder->end
1009         ];
1010
1011         my $expected = join( ", ", @expected );
1012
1013     # With no args, we should run "index"
1014         ok( my $response = request('http://localhost/argsorder/'),
1015             'Correct arg order ran' );
1016         is( $response->header('X-Catalyst-Executed'),
1017             $expected, 'Executed actions' );
1018         is( $response->content, 'base; ; index; ', 'Content OK' );
1019
1020     # With args given, run "all"
1021         ok( $response = request('http://localhost/argsorder/X'),
1022             'Correct arg order ran' );
1023         is( $response->header('X-Catalyst-Executed'), 
1024         join(", ", 
1025          qw[
1026              TestApp::Controller::Action::Chained->begin
1027              TestApp::Controller::Action::Chained::ArgsOrder->base
1028              TestApp::Controller::Action::Chained::ArgsOrder->all
1029              TestApp::Controller::Action::Chained::ArgsOrder->end
1030           ])
1031       );
1032         is( $response->content, 'base; ; all; X', 'Content OK' );
1033     
1034     }
1035
1036     #
1037     #   PathPrefix
1038     #
1039     {
1040         my @expected = qw[
1041           TestApp::Controller::Action::Chained->begin
1042           TestApp::Controller::Action::Chained::PathPrefix->instance
1043           TestApp::Controller::Action::Chained->end
1044         ];
1045
1046         my $expected = join( ", ", @expected );
1047
1048         ok( my $response = request('http://localhost/action/chained/pathprefix/1'),
1049             "PathPrefix (as an endpoint)" );
1050         is( $response->header('X-Catalyst-Executed'),
1051             $expected, 'Executed actions' );
1052         is( $response->content, '; 1', 'Content OK' );
1053     }
1054
1055     #
1056     #   static paths vs. captures
1057     #
1058     {
1059         my @expected = qw[
1060             TestApp::Controller::Action::Chained->begin
1061             TestApp::Controller::Action::Chained->apan
1062             TestApp::Controller::Action::Chained->korv
1063             TestApp::Controller::Action::Chained->static_end
1064             TestApp::Controller::Action::Chained->end
1065         ];
1066
1067         my $expected = join( ", ", @expected );
1068
1069         ok( my $response = request('http://localhost/action/chained/static_end'),
1070             "static paths are prefered over captures" );
1071         is( $response->header('X-Catalyst-Executed'),
1072             $expected, 'Executed actions' );
1073     }
1074     
1075     #
1076     #   */search
1077     #   doc/*
1078     # 
1079     #   request for doc/search should end up in doc/*
1080     {
1081         my @expected = qw[
1082             TestApp::Controller::Action::Chained->begin
1083             TestApp::Controller::Action::Chained->doc_star
1084             TestApp::Controller::Action::Chained->end
1085         ];
1086
1087         my $expected = join( ", ", @expected );
1088
1089         ok( my $response = request('http://localhost/chained/doc/search'),
1090             "we prefer static path parts earlier in the chain" );
1091         TODO: {
1092             local $TODO = 'gbjk never got off his ass and fixed this';
1093             is( $response->header('X-Catalyst-Executed'),
1094                 $expected, 'Executed actions' );
1095         }
1096     }
1097
1098     {
1099         ok( my $content =
1100             get('http://localhost/chained/capture%2Farg%3B/return_arg/foo%2Fbar%3B'),
1101             'request with URI-encoded arg' );
1102         like( $content, qr{foo/bar;\z}, 'args decoded' );
1103         like( $content, qr{capture/arg;}, 'captureargs decoded' );
1104     }
1105     {
1106         ok( my $content =
1107             get('http://localhost/chained/return_arg_decoded/foo%2Fbar%3B'),
1108             'request with URI-encoded arg' );
1109         like( $content, qr{foo/bar;\z}, 'args decoded' );
1110     }
1111
1112     # Test round tripping, specifically the / character %2F in uri_for:
1113     # not being able to feed it back action + captureargs and args into uri for
1114     # and result in the original request uri is a major piece of suck ;)
1115     foreach my $thing (
1116         ['foo', 'bar'],
1117         ['foo%2Fbar', 'baz'],
1118         ['foo', 'bar%2Fbaz'],
1119         ['foo%2Fbar', 'baz%2Fquux'],
1120         ['foo%2Fbar', 'baz%2Fquux', { foo => 'bar', 'baz' => 'quux%2Ffrood'}],
1121         ['foo%2Fbar', 'baz%2Fquux', { foo => 'bar', 'baz%2Ffnoo' => 'quux%2Ffrood'}],
1122         ['h%C3%BCtte', 'h%C3%BCtte', { test => 'h%C3%BCtte' } ],
1123     ) {
1124         my $path = '/chained/roundtrip_urifor/' .
1125             $thing->[0] . '/' . $thing->[1];
1126         $path .= '?' . join('&',
1127             map { $_ .'='. $thing->[2]->{$_}}
1128             sort keys %{$thing->[2]}) if $thing->[2];
1129         ok( my $content =
1130             get('http://localhost/' . $path),
1131             'request ' . $path . ' ok');
1132         my $exp = URI->new('http://localhost:3000' . $path);
1133         my ($want) = $content =~ m{/chained/(.*)};
1134         my $got = URI->new('http://localhost:3000/chained/' . $want);
1135         # Just check that the path matches, as who the hell knows or cares
1136         # where the app is based (live tests etc)
1137         is $got->path, $exp->path, "uri $path can round trip through uri_for (path)"
1138             or diag("Expected $path, got $content");
1139         is_deeply $got->query_form_hash, $exp->query_form_hash, "uri $path can round trip through uri_for (query)"
1140             or diag("Expected $path, got $content");
1141     }
1142
1143     #
1144     #   match_captures
1145     #
1146     {
1147
1148         ok( my $response = request('http://localhost/chained/match_captures/foo/bar'), 'match_captures: falling through' );
1149         is($response->header('X-TestAppActionTestMatchCaptures'), 'fallthrough', 'match_captures: fell through');
1150
1151         ok($response = request('http://localhost/chained/match_captures/force/bar'), 'match_captures: *not* falling through' );
1152         is($response->header('X-TestAppActionTestMatchCaptures'), 'forcing', 'match_captures: forced');
1153         is($response->header('X-TestAppActionTestMatchCapturesHasRan'), 'yes', 'match_captures: actually ran');
1154     }
1155 }
1156
1157 done_testing;
1158