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