From: Marcus Ramberg Date: Mon, 12 Jan 2009 12:07:37 +0000 (+0000) Subject: Backport go/visit from 5.8 (janh), prepare for dev release X-Git-Tag: 5.7099_04~2 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=f9bcc1280f6685f1d937688ed9c035bc9994a01e;hp=108201b5c403a8db7f6fa7726110b1d1b1caac1b;p=catagits%2FCatalyst-Runtime.git Backport go/visit from 5.8 (janh), prepare for dev release --- diff --git a/Changes b/Changes index 406c65a..5d78e99 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,6 @@ # This file documents the revision history for Perl extension Catalyst. -5.7XXXXXX XXXX +5.7099_04 2009-01-12 13:06:00 - Add environment hack for FastCGI under IIS (Simon Bertrang) - Test for this and preexisting Lighty hack (Simon Bertrang) - Change streaming test to serve itself rather than 01use.t, making test diff --git a/lib/Catalyst.pm b/lib/Catalyst.pm index f726932..4098e36 100644 --- a/lib/Catalyst.pm +++ b/lib/Catalyst.pm @@ -49,6 +49,7 @@ our $COUNT = 1; our $START = time; our $RECURSION = 1000; our $DETACH = "catalyst_detach\n"; +our $GO = "catalyst_go\n"; __PACKAGE__->mk_classdata($_) for qw/components arguments dispatcher engine log dispatcher_class @@ -63,7 +64,7 @@ __PACKAGE__->stats_class('Catalyst::Stats'); # Remember to update this in Catalyst::Runtime as well! -our $VERSION = '5.7099_03'; +our $VERSION = '5.7099_04'; sub import { my ( $class, @arguments ) = @_; @@ -327,6 +328,40 @@ When called with no arguments it escapes the processing chain entirely. sub detach { my $c = shift; $c->dispatcher->detach( $c, @_ ) } +=head2 $c->visit( $action [, \@arguments ] ) + +=head2 $c->visit( $class, $method, [, \@arguments ] ) + +Almost the same as C, but does a full dispatch, instead of just +calling the new C<$action> / C<$class-E$method>. This means that C, +C and the method you go to are called, just like a new request. + +C<$c-Estash> is kept unchanged. + +In effect, C allows you to "wrap" another action, just as it +would have been called by dispatching from a URL, while the analogous +C allows you to transfer control to another action as if it had +been reached directly from a URL. + +=cut + +sub visit { my $c = shift; $c->dispatcher->visit( $c, @_ ) } + +=head2 $c->go( $action [, \@arguments ] ) + +=head2 $c->go( $class, $method, [, \@arguments ] ) + +Almost the same as C, but does a full dispatch like C, +instead of just calling the new C<$action> / +C<$class-E$method>. This means that C, C and the +method you visit are called, just like a new request. + +C<$c-Estash> is kept unchanged. + +=cut + +sub go { my $c = shift; $c->dispatcher->go( $c, @_ ) } + =head2 $c->response =head2 $c->res @@ -1339,6 +1374,9 @@ sub execute { if ( !ref($error) and $error eq $DETACH ) { die $DETACH if($c->depth > 1); } + elsif ( !ref($error) and $error eq $GO ) { + die $GO if($c->depth > 0); + } else { unless ( ref $error ) { no warnings 'uninitialized'; @@ -2554,6 +2592,8 @@ Ulf Edvinsson willert: Sebastian Willert +batman: Jan Henning Thorsen + =head1 LICENSE This library is free software, you can redistribute it and/or modify it under diff --git a/lib/Catalyst/DispatchType.pm b/lib/Catalyst/DispatchType.pm index a3f271e..ce16150 100644 --- a/lib/Catalyst/DispatchType.pm +++ b/lib/Catalyst/DispatchType.pm @@ -46,6 +46,15 @@ Should return true if it registers something, or false otherwise. sub register { } +=head2 $self->expand_action + +Default fallback, returns nothing. See L for more info +about expand_action. + +=cut + +sub expand_action { } + =head2 $self->uri_for_action( $action, \@captures ) abstract method, to be implemented by dispatchtypes. Takes a diff --git a/lib/Catalyst/DispatchType/Chained.pm b/lib/Catalyst/DispatchType/Chained.pm index df9c1a6..4862310 100644 --- a/lib/Catalyst/DispatchType/Chained.pm +++ b/lib/Catalyst/DispatchType/Chained.pm @@ -294,6 +294,31 @@ sub uri_for_action { } +=head2 $c->expand_action($action) + +Return a list of actions that represents a chained action. See +L for more info. You probably want to +use the expand_action it provides rather than this directly. + +=cut + +sub expand_action { + my ($self, $action) = @_; + + return unless $action->attributes && $action->attributes->{Chained}; + + my @chain; + my $curr = $action; + + while ($curr) { + push @chain, $curr; + my $parent = $curr->attributes->{Chained}->[0]; + $curr = $self->{'actions'}{$parent}; + } + + return Catalyst::ActionChain->from_chain([reverse @chain]); +} + =head1 USAGE =head2 Introduction diff --git a/lib/Catalyst/Dispatcher.pm b/lib/Catalyst/Dispatcher.pm index 0f341de..9ebd2b9 100644 --- a/lib/Catalyst/Dispatcher.pm +++ b/lib/Catalyst/Dispatcher.pm @@ -151,9 +151,14 @@ sub _command2action { my $action; - # go to a string path ("/foo/bar/gorch") - # or action object which stringifies to that - $action = $self->_invoke_as_path( $c, "$command", \@args ); + if (Scalar::Util::blessed($command) && $command->isa('Catalyst::Action')) { + $action = $command; + } + else { + # go to a string path ("/foo/bar/gorch") + # or action object which stringifies to that + $action = $self->_invoke_as_path( $c, "$command", \@args ); + } # go to a component ( "MyApp::*::Foo" or $c->component("...") # - a path or an object) @@ -165,6 +170,67 @@ sub _command2action { return $action, \@args; } +=head2 $self->visit( $c, $command [, \@arguments ] ) + +Documented in L + +=cut + +sub visit { + my $self = shift; + $self->_do_visit('visit', @_); +} + +sub _do_visit { + my $self = shift; + my $opname = shift; + my ( $c, $command ) = @_; + my ( $action, $args ) = $self->_command2action(@_); + my $error = qq/Couldn't $opname("$command"): /; + + if (!$action) { + $error .= qq/Couldn't $opname to command "$command": / + .qq/Invalid action or component./; + } + elsif (!defined $action->namespace) { + $error .= qq/Action has no namespace: cannot $opname() to a plain / + .qq/method or component, must be a :Action or some sort./ + } + elsif (!$action->class->can('_DISPATCH')) { + $error .= qq/Action cannot _DISPATCH. / + .qq/Did you try to $opname() a non-controller action?/; + } + else { + $error = q(); + } + + if($error) { + $c->error($error); + $c->log->debug($error) if $c->debug; + return 0; + } + + $action = $self->expand_action($action); + + local $c->request->{arguments} = $args; + local $c->{namespace} = $action->{'namespace'}; + local $c->{action} = $action; + + $self->dispatch($c); +} + +=head2 $self->go( $c, $command [, \@arguments ] ) + +Documented in L + +=cut + +sub go { + my $self = shift; + $self->_do_visit('go', @_); + die $Catalyst::GO; +} + =head2 $self->forward( $c, $command [, \@arguments ] ) Documented in L @@ -392,6 +458,25 @@ sub uri_for_action { return undef; } +=head2 expand_action + +expand an action into a full representation of the dispatch. +mostly useful for chained, other actions will just return a +single action. + +=cut + +sub expand_action { + my ($self, $action) = @_; + + foreach my $dispatch_type (@{ $self->dispatch_types }) { + my $expanded = $dispatch_type->expand_action($action); + return $expanded if $expanded; + } + + return $action; +} + =head2 $self->register( $c, $action ) Make sure all required dispatch types for this action are loaded, then diff --git a/lib/Catalyst/Runtime.pm b/lib/Catalyst/Runtime.pm index 6c680a8..e04753b 100644 --- a/lib/Catalyst/Runtime.pm +++ b/lib/Catalyst/Runtime.pm @@ -7,7 +7,7 @@ BEGIN { require 5.008001; } # Remember to update this in Catalyst as well! -our $VERSION='5.7099_03'; +our $VERSION='5.7099_04'; $VERSION= eval $VERSION; diff --git a/t/lib/TestApp.pm b/t/lib/TestApp.pm index 83eb942..7c203b0 100644 --- a/t/lib/TestApp.pm +++ b/t/lib/TestApp.pm @@ -77,6 +77,11 @@ sub class_go_test_method :Private { $c->response->headers->header( 'X-Class-Go-Test-Method' => 1 ); } +sub class_visit_test_method :Private { + my ( $self, $c ) = @_; + $c->response->headers->header( 'X-Class-Visit-Test-Method' => 1 ); +} + sub loop_test : Local { my ( $self, $c ) = @_; diff --git a/t/lib/TestApp/Controller/Action/Go.pm b/t/lib/TestApp/Controller/Action/Go.pm index 0b12a60..9c7f3e6 100644 --- a/t/lib/TestApp/Controller/Action/Go.pm +++ b/t/lib/TestApp/Controller/Action/Go.pm @@ -25,7 +25,7 @@ sub four : Private { sub five : Local { my ( $self, $c ) = @_; - $c->go('View::Dump::Request'); + $c->forward('View::Dump::Request'); } sub inheritance : Local { @@ -66,6 +66,18 @@ sub go_chained : Local { $c->go('/action/chained/foo/spoon',[1]); } +sub view : Local { + my ( $self, $c, $val ) = @_; + eval { $c->go('View::Dump') }; + $c->res->body( $@ ? $@ : "go() did not die" ); +} + +sub model : Local { + my ( $self, $c, $val ) = @_; + eval { $c->go('Model::Foo') }; + $c->res->body( $@ ? $@ : "go() did not die" ); +} + sub args_embed_relative : Local { my ( $self, $c ) = @_; $c->go('embed/ok'); diff --git a/t/lib/TestApp/Controller/Action/TestRelative.pm b/t/lib/TestApp/Controller/Action/TestRelative.pm index 2efe3f1..6242093 100644 --- a/t/lib/TestApp/Controller/Action/TestRelative.pm +++ b/t/lib/TestApp/Controller/Action/TestRelative.pm @@ -26,4 +26,15 @@ sub relative_go_two : Local { my ( $self, $c ) = @_; $c->go( 'TestApp::Controller::Action::Go', 'one' ); } + +sub relative_visit : Local { + my ( $self, $c ) = @_; + $c->visit('/action/visit/one'); +} + +sub relative_visit_two : Local { + my ( $self, $c ) = @_; + $c->visit( 'TestApp::Controller::Action::Visit', 'one' ); +} + 1; diff --git a/t/lib/TestApp/Controller/Action/Visit.pm b/t/lib/TestApp/Controller/Action/Visit.pm new file mode 100644 index 0000000..3011a75 --- /dev/null +++ b/t/lib/TestApp/Controller/Action/Visit.pm @@ -0,0 +1,101 @@ +package TestApp::Controller::Action::Visit; + +use strict; +use base 'TestApp::Controller::Action'; + +sub one : Local { + my ( $self, $c ) = @_; + $c->visit('two'); +} + +sub two : Private { + my ( $self, $c ) = @_; + $c->visit('three'); +} + +sub three : Local { + my ( $self, $c ) = @_; + $c->visit( $self, 'four' ); +} + +sub four : Private { + my ( $self, $c ) = @_; + $c->visit('/action/visit/five'); +} + +sub five : Local { + my ( $self, $c ) = @_; + $c->forward('View::Dump::Request'); +} + +sub inheritance : Local { + my ( $self, $c ) = @_; + $c->visit('/action/inheritance/a/b/default'); +} + +sub global : Local { + my ( $self, $c ) = @_; + $c->visit('/global_action'); +} + +sub with_args : Local { + my ( $self, $c, $arg ) = @_; + $c->visit( 'args', [$arg] ); +} + +sub with_method_and_args : Local { + my ( $self, $c, $arg ) = @_; + $c->visit( qw/TestApp::Controller::Action::Visit args/, [$arg] ); +} + +sub args : Local { + my ( $self, $c, $val ) = @_; + die "passed argument does not match args" unless $val eq $c->req->args->[0]; + $c->res->body($val); +} + +sub visit_die : Local { + my ( $self, $c, $val ) = @_; + eval { $c->visit( 'args', [qq/new/] ) }; + $c->res->body( $@ ? $@ : "visit() doesn't die" ); +} + +sub visit_chained : Local { + my ( $self, $c, $val ) = @_; + $c->visit('/action/chained/foo/spoon',[1]); +} + +sub view : Local { + my ( $self, $c, $val ) = @_; + eval { $c->visit('View::Dump') }; + $c->res->body( $@ ? $@ : "visit() did not die" ); +} + +sub model : Local { + my ( $self, $c, $val ) = @_; + eval { $c->visit('Model::Foo') }; + $c->res->body( $@ ? $@ : "visit() did not die" ); +} + +sub args_embed_relative : Local { + my ( $self, $c ) = @_; + $c->visit('embed/ok'); +} + +sub args_embed_absolute : Local { + my ( $self, $c ) = @_; + $c->visit('/action/visit/embed/ok'); +} + +sub embed : Local { + my ( $self, $c, $ok ) = @_; + $ok ||= 'not ok'; + $c->res->body($ok); +} + +sub class_visit_test_action : Local { + my ( $self, $c ) = @_; + $c->visit(qw/TestApp class_visit_test_method/); +} + +1; diff --git a/t/lib/TestApp/View/Dump.pm b/t/lib/TestApp/View/Dump.pm index ad0f546..7acfa6d 100644 --- a/t/lib/TestApp/View/Dump.pm +++ b/t/lib/TestApp/View/Dump.pm @@ -1,7 +1,7 @@ package TestApp::View::Dump; use strict; -use base 'Catalyst::Base'; +use base 'Catalyst::View'; use Data::Dumper (); use Scalar::Util qw(weaken); diff --git a/t/live_component_controller_action_go.t b/t/live_component_controller_action_go.t new file mode 100644 index 0000000..407d4d2 --- /dev/null +++ b/t/live_component_controller_action_go.t @@ -0,0 +1,277 @@ +#!perl + +use strict; +use warnings; + +use FindBin; +use lib "$FindBin::Bin/../lib"; + +our $iters; + +BEGIN { $iters = $ENV{CAT_BENCH_ITERS} || 1; } + +use Test::More tests => 54 * $iters; +use Catalyst; +use Catalyst::Test 'TestApp'; + +if ( $ENV{CAT_BENCHMARK} ) { + require Benchmark; + Benchmark::timethis( $iters, \&run_tests ); +} +else { + for ( 1 .. $iters ) { + run_tests(); + } +} + +sub run_tests { + { + # Test go to global private action + ok( my $response = request('http://localhost/action/go/global'), + 'Request' ); + ok( $response->is_success, 'Response Successful 2xx' ); + is( $response->content_type, 'text/plain', 'Response Content-Type' ); + is( $response->header('X-Catalyst-Action'), + 'action/go/global', 'Main Class Action' ); + } + + { + my @expected = qw[ + TestApp::Controller::Action::Go->one + TestApp::Controller::Action::Go->two + TestApp::Controller::Action::Go->three + TestApp::Controller::Action::Go->four + TestApp::Controller::Action::Go->five + TestApp::View::Dump::Request->process + TestApp->end + ]; + + @expected = map { /Action/ ? (_begin($_), $_) : ($_) } @expected; + my $expected = join( ", ", @expected ); + + # Test go to chain of actions. + ok( my $response = request('http://localhost/action/go/one'), + 'Request' ); + ok( $response->is_success, 'Response Successful 2xx' ); + is( $response->content_type, 'text/plain', 'Response Content-Type' ); + is( $response->header('X-Catalyst-Action'), + 'action/go/one', 'Test Action' ); + is( + $response->header('X-Test-Class'), + 'TestApp::Controller::Action::Go', + 'Test Class' + ); + is( $response->header('X-Catalyst-Executed'), + $expected, 'Executed actions' ); + like( + $response->content, + qr/^bless\( .* 'Catalyst::Request' \)$/s, + 'Content is a serialized Catalyst::Request' + ); + } + + { + my @expected = qw[ + TestApp::Controller::Action::Go->go_die + TestApp::Controller::Action::Go->args + TestApp->end + ]; + + @expected = map { /Action/ ? (_begin($_), $_) : ($_) } @expected; + my $expected = join( ", ", @expected ); + + ok( my $response = request('http://localhost/action/go/go_die'), + 'Request' ); + ok( $response->is_success, 'Response Successful 2xx' ); + is( $response->content_type, 'text/plain', 'Response Content-Type' ); + is( $response->header('X-Catalyst-Action'), + 'action/go/go_die', 'Test Action' + ); + is( + $response->header('X-Test-Class'), + 'TestApp::Controller::Action::Go', + 'Test Class' + ); + is( $response->header('X-Catalyst-Executed'), + $expected, 'Executed actions' ); + is( $response->content, $Catalyst::GO, "Go died as expected" ); + } + { + ok( + my $response = request('http://localhost/action/go/model'), + 'Request with args' + ); + is( $response->content, + q[FATAL ERROR: Couldn't go("Model::Foo"): Action cannot _DISPATCH. Did you try to go() a non-controller action?], + q[go('Model::...') test] + ); + } + { + ok( + my $response = request('http://localhost/action/go/view'), + 'Request with args' + ); + is( $response->content, + q[FATAL ERROR: Couldn't go("View::Dump"): Action cannot _DISPATCH. Did you try to go() a non-controller action?], + q[go('View::...') test] + ); + } + { + ok( + my $response = + request('http://localhost/action/go/with_args/old'), + 'Request with args' + ); + ok( $response->is_success, 'Response Successful 2xx' ); + is( $response->content, 'old', 'go() with args (old)' ); + } + + { + ok( + my $response = request( + 'http://localhost/action/go/with_method_and_args/new'), + 'Request with args and method' + ); + ok( $response->is_success, 'Response Successful 2xx' ); + is( $response->content, 'new', 'go() with args (new)' ); + } + + # test go with embedded args + { + ok( + my $response = + request('http://localhost/action/go/args_embed_relative'), + 'Request' + ); + ok( $response->is_success, 'Response Successful 2xx' ); + is( $response->content, 'ok', 'go() with args_embed_relative' ); + } + + { + ok( + my $response = + request('http://localhost/action/go/args_embed_absolute'), + 'Request' + ); + ok( $response->is_success, 'Response Successful 2xx' ); + is( $response->content, 'ok', 'go() with args_embed_absolute' ); + } + { + my @expected = qw[ + TestApp::Controller::Action::TestRelative->relative_go + TestApp::Controller::Action::Go->one + TestApp::Controller::Action::Go->two + TestApp::Controller::Action::Go->three + TestApp::Controller::Action::Go->four + TestApp::Controller::Action::Go->five + TestApp::View::Dump::Request->process + TestApp->end + ]; + + @expected = map { /Action/ ? (_begin($_), $_) : ($_) } @expected; + my $expected = join( ", ", @expected ); + + # Test go to chain of actions. + ok( my $response = request('http://localhost/action/relative/relative_go'), + 'Request' ); + ok( $response->is_success, 'Response Successful 2xx' ); + is( $response->content_type, 'text/plain', 'Response Content-Type' ); + is( $response->header('X-Catalyst-Action'), + 'action/relative/relative_go', 'Test Action' ); + is( + $response->header('X-Test-Class'), + 'TestApp::Controller::Action::Go', + 'Test Class' + ); + is( $response->header('X-Catalyst-Executed'), + $expected, 'Executed actions' ); + like( + $response->content, + qr/^bless\( .* 'Catalyst::Request' \)$/s, + 'Content is a serialized Catalyst::Request' + ); + } + { + my @expected = qw[ + TestApp::Controller::Action::TestRelative->relative_go_two + TestApp::Controller::Action::Go->one + TestApp::Controller::Action::Go->two + TestApp::Controller::Action::Go->three + TestApp::Controller::Action::Go->four + TestApp::Controller::Action::Go->five + TestApp::View::Dump::Request->process + TestApp->end + ]; + + @expected = map { /Action/ ? (_begin($_), $_) : ($_) } @expected; + my $expected = join( ", ", @expected ); + + # Test go to chain of actions. + ok( + my $response = + request('http://localhost/action/relative/relative_go_two'), + 'Request' + ); + ok( $response->is_success, 'Response Successful 2xx' ); + is( $response->content_type, 'text/plain', 'Response Content-Type' ); + is( + $response->header('X-Catalyst-Action'), + 'action/relative/relative_go_two', + 'Test Action' + ); + is( + $response->header('X-Test-Class'), + 'TestApp::Controller::Action::Go', + 'Test Class' + ); + is( $response->header('X-Catalyst-Executed'), + $expected, 'Executed actions' ); + like( + $response->content, + qr/^bless\( .* 'Catalyst::Request' \)$/s, + 'Content is a serialized Catalyst::Request' + ); + } + + # test class go -- MUST FAIL! + { + ok( + my $response = request( + 'http://localhost/action/go/class_go_test_action'), + 'Request' + ); + ok( !$response->is_success, 'Response Fails' ); + is( $response->content, + q(FATAL ERROR: Couldn't go("TestApp"): Action has no namespace: cannot go() to a plain method or component, must be a :Action or some sort.), + 'Error message' + ); + } + + { + my @expected = qw[ + TestApp::Controller::Action::Go->begin + TestApp::Controller::Action::Go->go_chained + TestApp::Controller::Action::Chained->begin + TestApp::Controller::Action::Chained->foo + TestApp::Controller::Action::Chained::Foo->spoon + TestApp::Controller::Action::Chained->end + ]; + + my $expected = join( ", ", @expected ); + + ok( my $response = request('http://localhost/action/go/go_chained'), 'go to chained + subcontroller endpoint' ); + is( $response->header('X-Catalyst-Executed'), + $expected, 'Executed actions' ); + is( $response->content, '; 1', 'Content OK' ); + } + +} + + + +sub _begin { + local $_ = shift; + s/->(.*)$/->begin/; + return $_; +} + diff --git a/t/live_component_controller_action_visit.t b/t/live_component_controller_action_visit.t new file mode 100644 index 0000000..96fe762 --- /dev/null +++ b/t/live_component_controller_action_visit.t @@ -0,0 +1,289 @@ +#!perl + +use strict; +use warnings; + +use FindBin; +use lib "$FindBin::Bin/../lib"; + +our $iters; + +BEGIN { $iters = $ENV{CAT_BENCH_ITERS} || 1; } + +use Test::More tests => 54 * $iters; +use Catalyst::Test 'TestApp'; + +if ( $ENV{CAT_BENCHMARK} ) { + require Benchmark; + Benchmark::timethis( $iters, \&run_tests ); +} +else { + for ( 1 .. $iters ) { + run_tests(); + } +} + +sub run_tests { + { + # Test visit to global private action + ok( my $response = request('http://localhost/action/visit/global'), + 'Request' ); + ok( $response->is_success, 'Response Successful 2xx' ); + is( $response->content_type, 'text/plain', 'Response Content-Type' ); + is( $response->header('X-Catalyst-Action'), + 'action/visit/global', 'Main Class Action' ); + } + + { + my @expected = qw[ + TestApp::Controller::Action::Visit->one + TestApp::Controller::Action::Visit->two + TestApp::Controller::Action::Visit->three + TestApp::Controller::Action::Visit->four + TestApp::Controller::Action::Visit->five + TestApp::View::Dump::Request->process + TestApp->end + TestApp->end + TestApp->end + TestApp->end + TestApp->end + ]; + + @expected = map { /Action/ ? (_begin($_), $_) : ($_) } @expected; + my $expected = join( ", ", @expected ); + + # Test visit to chain of actions. + ok( my $response = request('http://localhost/action/visit/one'), + 'Request' ); + ok( $response->is_success, 'Response Successful 2xx' ); + is( $response->content_type, 'text/plain', 'Response Content-Type' ); + is( $response->header('X-Catalyst-Action'), + 'action/visit/one', 'Test Action' ); + is( + $response->header('X-Test-Class'), + 'TestApp::Controller::Action::Visit', + 'Test Class' + ); + is( $response->header('X-Catalyst-Executed'), + $expected, 'Executed actions' ); + like( + $response->content, + qr/^bless\( .* 'Catalyst::Request' \)$/s, + 'Content is a serialized Catalyst::Request' + ); + } + { + my @expected = qw[ + TestApp::Controller::Action::Visit->visit_die + TestApp::Controller::Action::Visit->args + TestApp->end + TestApp->end + ]; + + @expected = map { /Action/ ? (_begin($_), $_) : ($_) } @expected; + my $expected = join( ", ", @expected ); + + ok( my $response = request('http://localhost/action/visit/visit_die'), + 'Request' ); + ok( $response->is_success, 'Response Successful 2xx' ); + is( $response->content_type, 'text/plain', 'Response Content-Type' ); + is( $response->header('X-Catalyst-Action'), + 'action/visit/visit_die', 'Test Action' + ); + is( + $response->header('X-Test-Class'), + 'TestApp::Controller::Action::Visit', + 'Test Class' + ); + is( $response->header('X-Catalyst-Executed'), + $expected, 'Executed actions' ); + is( $response->content, "visit() doesn't die", "Visit does not die" ); + } + { + ok( + my $response = request('http://localhost/action/visit/model'), + 'Request with args' + ); + is( $response->content, + q[FATAL ERROR: Couldn't visit("Model::Foo"): Action cannot _DISPATCH. Did you try to visit() a non-controller action?] + ); + } + { + ok( + my $response = request('http://localhost/action/visit/view'), + 'Request with args' + ); + is( $response->content, + q[FATAL ERROR: Couldn't visit("View::Dump"): Action cannot _DISPATCH. Did you try to visit() a non-controller action?] + ); + } + { + ok( + my $response = + request('http://localhost/action/visit/with_args/old'), + 'Request with args' + ); + ok( $response->is_success, 'Response Successful 2xx' ); + is( $response->content, 'old', 'visit() with args (old)' ); + } + + { + ok( + my $response = request( + 'http://localhost/action/visit/with_method_and_args/new'), + 'Request with args and method' + ); + ok( $response->is_success, 'Response Successful 2xx' ); + is( $response->content, 'new', 'visit() with args (new)' ); + } + + # test visit with embedded args + { + ok( + my $response = + request('http://localhost/action/visit/args_embed_relative'), + 'Request' + ); + ok( $response->is_success, 'Response Successful 2xx' ); + is( $response->content, 'ok', 'visit() with args_embed_relative' ); + } + + { + ok( + my $response = + request('http://localhost/action/visit/args_embed_absolute'), + 'Request' + ); + ok( $response->is_success, 'Response Successful 2xx' ); + is( $response->content, 'ok', 'visit() with args_embed_absolute' ); + } + { + my @expected = qw[ + TestApp::Controller::Action::TestRelative->relative_visit + TestApp::Controller::Action::Visit->one + TestApp::Controller::Action::Visit->two + TestApp::Controller::Action::Visit->three + TestApp::Controller::Action::Visit->four + TestApp::Controller::Action::Visit->five + TestApp::View::Dump::Request->process + TestApp->end + TestApp->end + TestApp->end + TestApp->end + TestApp->end + TestApp->end + ]; + + @expected = map { /Action/ ? (_begin($_), $_) : ($_) } @expected; + my $expected = join( ", ", @expected ); + + # Test visit to chain of actions. + ok( my $response = request('http://localhost/action/relative/relative_visit'), + 'Request' ); + ok( $response->is_success, 'Response Successful 2xx' ); + is( $response->content_type, 'text/plain', 'Response Content-Type' ); + is( $response->header('X-Catalyst-Action'), + 'action/relative/relative_visit', 'Test Action' ); + is( + $response->header('X-Test-Class'), + 'TestApp::Controller::Action::Visit', + 'Test Class' + ); + is( $response->header('X-Catalyst-Executed'), + $expected, 'Executed actions' ); + like( + $response->content, + qr/^bless\( .* 'Catalyst::Request' \)$/s, + 'Content is a serialized Catalyst::Request' + ); + } + { + my @expected = qw[ + TestApp::Controller::Action::TestRelative->relative_visit_two + TestApp::Controller::Action::Visit->one + TestApp::Controller::Action::Visit->two + TestApp::Controller::Action::Visit->three + TestApp::Controller::Action::Visit->four + TestApp::Controller::Action::Visit->five + TestApp::View::Dump::Request->process + TestApp->end + TestApp->end + TestApp->end + TestApp->end + TestApp->end + TestApp->end + ]; + + @expected = map { /Action/ ? (_begin($_), $_) : ($_) } @expected; + my $expected = join( ", ", @expected ); + + # Test visit to chain of actions. + ok( + my $response = + request('http://localhost/action/relative/relative_visit_two'), + 'Request' + ); + ok( $response->is_success, 'Response Successful 2xx' ); + is( $response->content_type, 'text/plain', 'Response Content-Type' ); + is( + $response->header('X-Catalyst-Action'), + 'action/relative/relative_visit_two', + 'Test Action' + ); + is( + $response->header('X-Test-Class'), + 'TestApp::Controller::Action::Visit', + 'Test Class' + ); + is( $response->header('X-Catalyst-Executed'), + $expected, 'Executed actions' ); + like( + $response->content, + qr/^bless\( .* 'Catalyst::Request' \)$/s, + 'Content is a serialized Catalyst::Request' + ); + } + + # test class visit -- MUST FAIL! + { + ok( + my $response = request( + 'http://localhost/action/visit/class_visit_test_action'), + 'Request' + ); + ok( !$response->is_success, 'Response Fails' ); + is( $response->content, + q[FATAL ERROR: Couldn't visit("TestApp"): Action has no namespace: cannot visit() to a plain method or component, must be a :Action or some sort.], + "Cannot visit app namespace" + ); + } + + { + my @expected = qw[ + TestApp::Controller::Action::Visit->begin + TestApp::Controller::Action::Visit->visit_chained + TestApp::Controller::Action::Chained->begin + TestApp::Controller::Action::Chained->foo + TestApp::Controller::Action::Chained::Foo->spoon + TestApp::Controller::Action::Chained->end + TestApp->end + ]; + + my $expected = join( ", ", @expected ); + + ok( my $response = request('http://localhost/action/visit/visit_chained'), 'visit to chained + subcontroller endpoint' ); + is( $response->header('X-Catalyst-Executed'), + $expected, 'Executed actions' ); + is( $response->content, '; 1', 'Content OK' ); + } + +} + + + +sub _begin { + local $_ = shift; + s/->(.*)$/->begin/; + return $_; +} + diff --git a/t/unit_core_mvc.t b/t/unit_core_mvc.t index 549d758..f641014 100644 --- a/t/unit_core_mvc.t +++ b/t/unit_core_mvc.t @@ -1,4 +1,4 @@ -use Test::More tests => 44; +use Test::More tests => 45; use strict; use warnings; @@ -154,6 +154,11 @@ is ( MyApp->model , 'MyApp::Model::M', 'default_model in class method ok'); *MyApp::Model::M::ACCEPT_CONTEXT = sub { my ($self, $c, @args) = @_; $args= \@args}; *MyApp::View::V::ACCEPT_CONTEXT = sub { my ($self, $c, @args) = @_; $args= \@args}; + # test accept-context with class rather than instance + MyApp->model('M', qw/foo bar/); + is_deeply($args, [qw/foo bar/], '$c->model args passed to ACCEPT_CONTEXT ok'); + + MyApp->model('M', qw/foo bar/); is_deeply($args, [qw/foo bar/], '$c->model args passed to ACCEPT_CONTEXT ok');