d6fcfedcc7eaf489e089e9faeca0ccb3c6ecae44
[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     #   Tests that an uri_for to a chained root index action
777     #   returns the right value.
778     #
779     {
780         ok( my $response = request(
781             'http://localhost/action/chained/to_root' ),
782             'uri_for with chained root action as arg' );
783         like( $response->content,
784             qr(URI:https?://[^/]+/),
785             'Correct URI generated' );
786     }
787
788     #
789     #   Test interception of recursive chains. This test was added because at
790     #   one point during the :Chained development, Catalyst used to hang on
791     #   recursive chains.
792     #
793     {
794         eval { require 'TestAppChainedRecursive.pm' };
795         if ($run_number == 1) {
796             ok( ! $@, "Interception of recursive chains" );
797         }
798         else { pass( "Interception of recursive chains already tested" ) }
799     }
800
801     #
802     #   Test failure of absolute path part arguments.
803     #
804     {
805         eval { require 'TestAppChainedAbsolutePathPart.pm' };
806         if ($run_number == 1) {
807             like( $@, qr(foo/foo),
808                 "Usage of absolute path part argument emits error" );
809         }
810         else { pass( "Error on absolute path part arguments already tested" ) }
811     }
812
813     #
814     #   Test chained actions in the root controller
815     #
816     {
817         my @expected = qw[
818           TestApp::Controller::Action::Chained::Root->rootsub
819           TestApp::Controller::Action::Chained::Root->endpointsub
820           TestApp::Controller::Root->end
821         ];
822
823         my $expected = join( ", ", @expected );
824
825         ok( my $response = request('http://localhost/rootsub/1/endpointsub/2'), 'chained in root namespace' );
826         is( $response->header('X-Catalyst-Executed'),
827             $expected, 'Executed actions' );
828         is( $response->content, '', 'Content OK' );
829     }
830
831     #
832     #   Complex path with multiple empty pathparts
833     #
834     {
835         my @expected = qw[
836           TestApp::Controller::Action::Chained->begin
837           TestApp::Controller::Action::Chained->mult_nopp_base
838           TestApp::Controller::Action::Chained->mult_nopp_all
839           TestApp::Controller::Action::Chained->end
840         ];
841
842         my $expected = join( ", ", @expected );
843
844         ok( my $response = request('http://localhost/chained/mult_nopp'),
845             "Complex path with multiple empty pathparts" );
846         is( $response->header('X-Catalyst-Executed'),
847             $expected, 'Executed actions' );
848         is( $response->content, '; ', 'Content OK' );
849     }
850
851     #
852     #   Complex path with multiple non-capturing pathparts
853     # PathPart('') CaptureArgs(0), PathPart('foo') CaptureArgs(0), PathPart('') Args(0)
854     # should win over PathPart('') CaptureArgs(1), PathPart('') Args(0)
855     #
856     {
857         my @expected = qw[
858           TestApp::Controller::Action::Chained->begin
859           TestApp::Controller::Action::Chained->mult_nopp2_base
860           TestApp::Controller::Action::Chained->mult_nopp2_nocap
861           TestApp::Controller::Action::Chained->mult_nopp2_action
862           TestApp::Controller::Action::Chained->mult_nopp2_action_default
863           TestApp::Controller::Action::Chained->end
864         ];
865
866         my $expected = join( ", ", @expected );
867
868         ok( my $response = request('http://localhost/chained/mult_nopp2/action'),
869             "Complex path with multiple non-capturing pathparts" );
870         is( $response->header('X-Catalyst-Executed'),
871             $expected, 'Executed actions' );
872         is( $response->content, '; ', 'Content OK' );
873     }
874
875     #
876     #   Higher Args() hiding more specific CaptureArgs chains sections
877     #
878     {
879         my @expected = qw[
880             TestApp::Controller::Action::Chained->begin
881             TestApp::Controller::Action::Chained->cc_base
882             TestApp::Controller::Action::Chained->cc_link
883             TestApp::Controller::Action::Chained->cc_anchor
884             TestApp::Controller::Action::Chained->end
885             ];
886
887         my $expected = join ', ', @expected;
888
889         ok( my $response = request('http://localhost/chained/choose_capture/anchor.html'),
890             'Choose between an early Args() and a later more ideal chain' );
891         is( $response->header('X-Catalyst-Executed') => $expected, 'Executed actions');
892         is( $response->content => '; ', 'Content OK' );
893     }
894
895     #
896     #   Less specific chain not being seen correctly due to earlier looser capture
897     #
898     {
899         my @expected = qw[
900             TestApp::Controller::Action::Chained->begin
901             TestApp::Controller::Action::Chained->cc_base
902             TestApp::Controller::Action::Chained->cc_b
903             TestApp::Controller::Action::Chained->cc_b_link
904             TestApp::Controller::Action::Chained->cc_b_anchor
905             TestApp::Controller::Action::Chained->end
906             ];
907
908         my $expected = join ', ', @expected;
909
910         ok( my $response = request('http://localhost/chained/choose_capture/b/a/anchor.html'),
911             'Choose between a more specific chain and an earlier looser one' );
912         is( $response->header('X-Catalyst-Executed') => $expected, 'Executed actions');
913         is( $response->content => 'a; ', 'Content OK' );
914     }
915
916     #
917     #   Check we get the looser one when it's the correct match
918     #
919     {
920         my @expected = qw[
921             TestApp::Controller::Action::Chained->begin
922             TestApp::Controller::Action::Chained->cc_base
923             TestApp::Controller::Action::Chained->cc_a
924             TestApp::Controller::Action::Chained->cc_a_link
925             TestApp::Controller::Action::Chained->cc_a_anchor
926             TestApp::Controller::Action::Chained->end
927             ];
928
929         my $expected = join ', ', @expected;
930
931         ok( my $response = request('http://localhost/chained/choose_capture/a/a/anchor.html'),
932             'Choose between a more specific chain and an earlier looser one' );
933         is( $response->header('X-Catalyst-Executed') => $expected, 'Executed actions');
934         is( $response->content => 'a; anchor.html', 'Content OK' );
935     }
936
937     # CaptureArgs(1) PathPart('...') should win over CaptureArgs(2) PathPart('')
938     {
939         my @expected = qw[
940           TestApp::Controller::Action::Chained->begin
941           TestApp::Controller::Action::Chained::CaptureArgs->base
942           TestApp::Controller::Action::Chained::CaptureArgs->one_arg
943           TestApp::Controller::Action::Chained::CaptureArgs->edit_one_arg
944           TestApp::Controller::Action::Chained::CaptureArgs->end
945         ];
946
947         my $expected = join( ", ", @expected );
948
949         # should dispatch to /base/one_args/edit_one_arg
950         ok( my $response = request('http://localhost/captureargs/one/edit'),
951             'Correct arg order ran' );
952         TODO: {
953         local $TODO = 'Known bug';
954         is( $response->header('X-Catalyst-Executed'),
955             $expected, 'Executed actions' );
956         is( $response->content, 'base; one_arg; edit_one_arg', 'Content OK' );
957         }
958     }
959
960     #  PathPart('...') Args(1) should win over CaptureArgs(2) PathPart('')
961     {
962         my @expected = qw[
963           TestApp::Controller::Action::Chained->begin
964           TestApp::Controller::Action::Chained::CaptureArgs->base
965           TestApp::Controller::Action::Chained::CaptureArgs->test_one_arg
966           TestApp::Controller::Action::Chained::CaptureArgs->end
967         ];
968
969         my $expected = join( ", ", @expected );
970
971         # should dispatch to /base/test_one_arg
972         ok( my $response = request('http://localhost/captureargs/test/one'),
973             'Correct pathpart/arg ran' );
974         TODO: {
975         local $TODO = 'Known bug';
976         is( $response->header('X-Catalyst-Executed'),
977             $expected, 'Executed actions' );
978         is( $response->content, 'base; test_plus_arg; one;', 'Content OK' );
979         }
980     }
981
982     #
983     #   Args(0) should win over Args() if we actually have no arguments.
984     {
985         my @expected = qw[
986           TestApp::Controller::Action::Chained->begin
987           TestApp::Controller::Action::Chained::ArgsOrder->base
988           TestApp::Controller::Action::Chained::ArgsOrder->index
989           TestApp::Controller::Action::Chained::ArgsOrder->end
990         ];
991
992         my $expected = join( ", ", @expected );
993
994     # With no args, we should run "index"
995         ok( my $response = request('http://localhost/argsorder/'),
996             'Correct arg order ran' );
997         is( $response->header('X-Catalyst-Executed'),
998             $expected, 'Executed actions' );
999         is( $response->content, 'base; ; index; ', 'Content OK' );
1000
1001     # With args given, run "all"
1002         ok( $response = request('http://localhost/argsorder/X'),
1003             'Correct arg order ran' );
1004         is( $response->header('X-Catalyst-Executed'), 
1005         join(", ", 
1006          qw[
1007              TestApp::Controller::Action::Chained->begin
1008              TestApp::Controller::Action::Chained::ArgsOrder->base
1009              TestApp::Controller::Action::Chained::ArgsOrder->all
1010              TestApp::Controller::Action::Chained::ArgsOrder->end
1011           ])
1012       );
1013         is( $response->content, 'base; ; all; X', 'Content OK' );
1014     
1015     }
1016
1017     #
1018     #   PathPrefix
1019     #
1020     {
1021         my @expected = qw[
1022           TestApp::Controller::Action::Chained->begin
1023           TestApp::Controller::Action::Chained::PathPrefix->instance
1024           TestApp::Controller::Action::Chained->end
1025         ];
1026
1027         my $expected = join( ", ", @expected );
1028
1029         ok( my $response = request('http://localhost/action/chained/pathprefix/1'),
1030             "PathPrefix (as an endpoint)" );
1031         is( $response->header('X-Catalyst-Executed'),
1032             $expected, 'Executed actions' );
1033         is( $response->content, '; 1', 'Content OK' );
1034     }
1035
1036     #
1037     #   static paths vs. captures
1038     #
1039     {
1040         my @expected = qw[
1041             TestApp::Controller::Action::Chained->begin
1042             TestApp::Controller::Action::Chained->apan
1043             TestApp::Controller::Action::Chained->korv
1044             TestApp::Controller::Action::Chained->static_end
1045             TestApp::Controller::Action::Chained->end
1046         ];
1047
1048         my $expected = join( ", ", @expected );
1049
1050         ok( my $response = request('http://localhost/action/chained/static_end'),
1051             "static paths are prefered over captures" );
1052         is( $response->header('X-Catalyst-Executed'),
1053             $expected, 'Executed actions' );
1054     }
1055     
1056     #
1057     #   */search
1058     #   doc/*
1059     # 
1060     #   request for doc/search should end up in doc/*
1061     {
1062         my @expected = qw[
1063             TestApp::Controller::Action::Chained->begin
1064             TestApp::Controller::Action::Chained->doc_star
1065             TestApp::Controller::Action::Chained->end
1066         ];
1067
1068         my $expected = join( ", ", @expected );
1069
1070         ok( my $response = request('http://localhost/chained/doc/search'),
1071             "we prefer static path parts earlier in the chain" );
1072         TODO: {
1073             local $TODO = 'gbjk never got off his ass and fixed this';
1074             is( $response->header('X-Catalyst-Executed'),
1075                 $expected, 'Executed actions' );
1076         }
1077     }
1078
1079     {
1080         ok( my $content =
1081             get('http://localhost/chained/capture%2Farg%3B/return_arg/foo%2Fbar%3B'),
1082             'request with URI-encoded arg' );
1083         like( $content, qr{foo/bar;\z}, 'args decoded' );
1084         like( $content, qr{capture/arg;}, 'captureargs decoded' );
1085     }
1086     {
1087         ok( my $content =
1088             get('http://localhost/chained/return_arg_decoded/foo%2Fbar%3B'),
1089             'request with URI-encoded arg' );
1090         like( $content, qr{foo/bar;\z}, 'args decoded' );
1091     }
1092
1093     # Test round tripping, specifically the / character %2F in uri_for:
1094     # not being able to feed it back action + captureargs and args into uri for
1095     # and result in the original request uri is a major piece of suck ;)
1096     foreach my $thing (
1097         ['foo', 'bar'],
1098         ['foo%2Fbar', 'baz'],
1099         ['foo', 'bar%2Fbaz'],
1100         ['foo%2Fbar', 'baz%2Fquux'],
1101         ['foo%2Fbar', 'baz%2Fquux', { foo => 'bar', 'baz' => 'quux%2Ffrood'}],
1102         ['foo%2Fbar', 'baz%2Fquux', { foo => 'bar', 'baz%2Ffnoo' => 'quux%2Ffrood'}],
1103         ['h%C3%BCtte', 'h%C3%BCtte', { test => 'h%C3%BCtte' } ],
1104     ) {
1105         my $path = '/chained/roundtrip_urifor/' .
1106             $thing->[0] . '/' . $thing->[1];
1107         $path .= '?' . join('&',
1108             map { $_ .'='. $thing->[2]->{$_}}
1109             sort keys %{$thing->[2]}) if $thing->[2];
1110         ok( my $content =
1111             get('http://localhost/' . $path),
1112             'request ' . $path . ' ok');
1113         my $exp = URI->new('http://localhost:3000' . $path);
1114         my ($want) = $content =~ m{/chained/(.*)};
1115         my $got = URI->new('http://localhost:3000/chained/' . $want);
1116         # Just check that the path matches, as who the hell knows or cares
1117         # where the app is based (live tests etc)
1118         is $got->path, $exp->path, "uri $path can round trip through uri_for (path)"
1119             or diag("Expected $path, got $content");
1120         is_deeply $got->query_form_hash, $exp->query_form_hash, "uri $path can round trip through uri_for (query)"
1121             or diag("Expected $path, got $content");
1122     }
1123
1124     #
1125     #   match_captures
1126     #
1127     {
1128
1129         ok( my $response = request('http://localhost/chained/match_captures/foo/bar'), 'match_captures: falling through' );
1130         is($response->header('X-TestAppActionTestMatchCaptures'), 'fallthrough', 'match_captures: fell through');
1131
1132         ok($response = request('http://localhost/chained/match_captures/force/bar'), 'match_captures: *not* falling through' );
1133         is($response->header('X-TestAppActionTestMatchCaptures'), 'forcing', 'match_captures: forced');
1134         is($response->header('X-TestAppActionTestMatchCapturesHasRan'), 'yes', 'match_captures: actually ran');
1135     }
1136 }
1137
1138 done_testing;
1139