TWMC: Doc patch and useragent patch from rT
[catagits/Test-WWW-Mechanize-Catalyst.git] / lib / Test / WWW / Mechanize / Catalyst.pm
CommitLineData
6bc86362 1package Test::WWW::Mechanize::Catalyst;
254eca41 2
f21ad406 3use Moose;
254eca41 4
5use Carp qw/croak/;
6require Catalyst::Test; # Do not call import
6bc86362 7use Encode qw();
8use HTML::Entities;
9use Test::WWW::Mechanize;
254eca41 10
f21ad406 11extends 'Test::WWW::Mechanize', 'Moose::Object';
12
d6fc3a22 13#use namespace::clean -execept => 'meta';
254eca41 14
dabec5e2 15our $VERSION = '0.50';
f21ad406 16our $APP_CLASS;
6bc86362 17my $Test = Test::Builder->new();
18
f21ad406 19has catalyst_app => (
20 is => 'ro',
ab0b00e3 21 predicate => 'has_catalyst_app',
f21ad406 22);
23
24has allow_external => (
25 is => 'rw',
26 isa => 'Bool',
27 default => 0
28);
6bc86362 29
1f8dbf85 30has host => (
31 is => 'rw',
32 isa => 'Str',
33 clearer => 'clear_host',
34 predicate => 'has_host',
35);
36
254eca41 37sub new {
f21ad406 38 my $class = shift;
254eca41 39
dabec5e2 40 my $args = ref $_[0] ? $_[0] : { @_ };
41
42 # Dont let LWP complain about options for our attributes
43 my %attr_options = map {
44 my $n = $_->init_arg;
45 defined $n && exists $args->{$n}
46 ? ( $n => delete $args->{$n} )
47 : ( );
48 } $class->meta->get_all_attributes;
49
50 my $obj = $class->SUPER::new(%$args);
f21ad406 51 my $self = $class->meta->new_object(
52 __INSTANCE__ => $obj,
ab0b00e3 53 ($APP_CLASS ? (catalyst_app => $APP_CLASS) : () ),
dabec5e2 54 %attr_options
f21ad406 55 );
56
dabec5e2 57 $self->BUILDALL;
58
59
60 return $self;
61}
62
63sub BUILD {
64 my ($self) = @_;
65
ab0b00e3 66 unless ($ENV{CATALYST_SERVER}) {
67 croak "catalyst_app attribute is required unless CATALYST_SERVER env variable is set"
68 unless $self->has_catalyst_app;
69 Class::MOP::load_class($self->catalyst_app)
70 unless (Class::MOP::is_class_loaded($self->catalyst_app));
71 }
6bc86362 72}
73
74sub _make_request {
75 my ( $self, $request ) = @_;
6bc86362 76
ab0b00e3 77 my $response = $self->_do_catalyst_request($request);
6bc86362 78 $response->header( 'Content-Base', $request->uri );
79 $response->request($request);
80 if ( $request->uri->as_string =~ m{^/} ) {
81 $request->uri(
82 URI->new( 'http://localhost:80/' . $request->uri->as_string ) );
83 }
84 $self->cookie_jar->extract_cookies($response) if $self->cookie_jar;
85
86 # fail tests under the Catalyst debug screen
87 if ( !$self->{catalyst_debug}
88 && $response->code == 500
89 && $response->content =~ /on Catalyst \d+\.\d+/ )
90 {
91 my ($error)
92 = ( $response->content =~ /<code class="error">(.*?)<\/code>/s );
93 $error ||= "unknown error";
94 decode_entities($error);
95 $Test->diag("Catalyst error screen: $error");
96 $response->content('');
97 $response->content_type('');
98 }
99
100 # check if that was a redirect
101 if ( $response->header('Location')
102 && $self->redirect_ok( $request, $response ) )
103 {
104
105 # remember the old response
106 my $old_response = $response;
107
108 # *where* do they want us to redirect to?
109 my $location = $old_response->header('Location');
110
111 # no-one *should* be returning non-absolute URLs, but if they
112 # are then we'd better cope with it. Let's create a new URI, using
113 # our request as the base.
114 my $uri = URI->new_abs( $location, $request->uri )->as_string;
115
116 # make a new response, and save the old response in it
117 $response = $self->_make_request( HTTP::Request->new( GET => $uri ) );
118 my $end_of_chain = $response;
119 while ( $end_of_chain->previous ) # keep going till the end
120 {
121 $end_of_chain = $end_of_chain->previous;
122 } # of the chain...
123 $end_of_chain->previous($old_response); # ...and add us to it
124 } else {
125 $response->{_raw_content} = $response->content;
126 }
127
128 return $response;
129}
130
ab0b00e3 131sub _do_catalyst_request {
132 my ($self, $request) = @_;
133
dabec5e2 134 my $uri = $request->uri;
135 $uri->scheme('http') unless defined $uri->scheme;
136 $uri->host('localhost') unless defined $uri->host;
137
d6fc3a22 138 $request = $self->prepare_request($request);
ab0b00e3 139 $self->cookie_jar->add_cookie_header($request) if $self->cookie_jar;
140
141 # Woe betide anyone who unsets CATALYST_SERVER
142 return Catalyst::Test::remote_request($request)
143 if $ENV{CATALYST_SERVER};
144
ab0b00e3 145
146
147 # If there's no Host header, set one.
148 unless ($request->header('Host')) {
149 my $host = $self->has_host
150 ? $self->host
151 : $uri->host;
152
153 $request->header('Host', $host);
154 }
155
156 if ( $self->{allow_external} ) {
157 unless ( $request->uri->as_string =~ m{^/}
158 || $request->uri->host eq 'localhost' )
159 {
160 return $self->SUPER::_make_request($request);
161 }
162 }
163
164 my @creds = $self->get_basic_credentials( "Basic", $uri );
165 $request->authorization_basic( @creds ) if @creds;
166
167 return Catalyst::Test::local_request($self->{catalyst_app}, $request);
168}
169
6bc86362 170sub import {
254eca41 171 my ($class, $app) = @_;
f21ad406 172
254eca41 173 if (defined $app) {
f21ad406 174 Class::MOP::load_class($app)
175 unless (Class::MOP::is_class_loaded($app));
254eca41 176 $APP_CLASS = $app;
177 }
f21ad406 178
6bc86362 179}
180
6bc86362 181
1821;
183
184__END__
185
186=head1 NAME
187
188Test::WWW::Mechanize::Catalyst - Test::WWW::Mechanize for Catalyst
189
190=head1 SYNOPSIS
191
192 # We're in a t/*.t test script...
ab0b00e3 193 use Test::WWW::Mechanize::Catalyst;
194
6bc86362 195 # To test a Catalyst application named 'Catty':
ab0b00e3 196 my $mech = Test::WWW::Mechanize::Catalyst->new(catalyst_app => 'Catty');
6bc86362 197
6bc86362 198 $mech->get_ok("/"); # no hostname needed
199 is($mech->ct, "text/html");
200 $mech->title_is("Root", "On the root page");
201 $mech->content_contains("This is the root page", "Correct content");
202 $mech->follow_link_ok({text => 'Hello'}, "Click on Hello");
203 # ... and all other Test::WWW::Mechanize methods
ab0b00e3 204
205 # White label site testing
206 $mech->host("foo.com");
207 $mech->get_ok("/");
6bc86362 208
209=head1 DESCRIPTION
210
d6fc3a22 211L<Catalyst> is an elegant MVC Web Application Framework.
212L<Test::WWW::Mechanize> is a subclass of L<WWW::Mechanize> that incorporates
213features for web application testing. The L<Test::WWW::Mechanize::Catalyst>
214module meshes the two to allow easy testing of L<Catalyst> applications without
215needing to starting up a web server.
6bc86362 216
217Testing web applications has always been a bit tricky, normally
d6fc3a22 218requiring starting a web server for your application and making real HTTP
6bc86362 219requests to it. This module allows you to test L<Catalyst> web
d6fc3a22 220applications but does not require a server or issue HTTP
6bc86362 221requests. Instead, it passes the HTTP request object directly to
222L<Catalyst>. Thus you do not need to use a real hostname:
223"http://localhost/" will do. However, this is optional. The following
224two lines of code do exactly the same thing:
225
226 $mech->get_ok('/action');
227 $mech->get_ok('http://localhost/action');
228
229Links which do not begin with / or are not for localhost can be handled
230as normal Web requests - this is handy if you have an external
231single sign-on system. You must set allow_external to true for this:
232
d6fc3a22 233 $mech->allow_external(1);
6bc86362 234
235You can also test a remote server by setting the environment variable
d6fc3a22 236CATALYST_SERVER; for example:
6bc86362 237
238 $ CATALYST_SERVER=http://example.com/myapp prove -l t
239
240will run the same tests on the application running at
241http://example.com/myapp regardless of whether or not you specify
242http:://localhost for Test::WWW::Mechanize::Catalyst.
d6fc3a22 243
244Furthermore, if you set CATALYST_SERVER, the server will be regarded
245as a remote server even if your links point to localhost. Thus, you
246can use Test::WWW::Mechanize::Catalyst to test your live webserver
247running on your local machine, if you need to test aspects of your
248deployment environment (for example, configuration options in an
249http.conf file) instead of just the Catalyst request handling.
6bc86362 250
251This makes testing fast and easy. L<Test::WWW::Mechanize> provides
252functions for common web testing scenarios. For example:
253
254 $mech->get_ok( $page );
255 $mech->title_is( "Invoice Status", "Make sure we're on the invoice page" );
256 $mech->content_contains( "Andy Lester", "My name somewhere" );
257 $mech->content_like( qr/(cpan|perl)\.org/, "Link to perl.org or CPAN" );
258
259This module supports cookies automatically.
260
261To use this module you must pass it the name of the application. See
262the SYNOPSIS above.
263
264Note that Catalyst has a special developing feature: the debug
265screen. By default this module will treat responses which are the
266debug screen as failures. If you actually want to test debug screens,
267please use:
268
d6fc3a22 269 $mmech->{catalyst_debug} = 1;
6bc86362 270
271An alternative to this module is L<Catalyst::Test>.
272
273=head1 CONSTRUCTOR
274
275=head2 new
276
d6fc3a22 277Behaves like, and calls, L<WWW::Mechanize>'s C<new> method. Any params
6bc86362 278passed in get passed to WWW::Mechanize's constructor. Note that we
279need to pass the name of the Catalyst application to the "use":
280
281 use Test::WWW::Mechanize::Catalyst 'Catty';
282 my $mech = Test::WWW::Mechanize::Catalyst->new;
283
284=head1 METHODS
285
286=head2 allow_external
287
288Links which do not begin with / or are not for localhost can be handled
289as normal Web requests - this is handy if you have an external
290single sign-on system. You must set allow_external to true for this:
291
292 $m->allow_external(1);
293
1f8dbf85 294head2 catalyst_app
295
296The name of the Catalyst app which we are testing against. Read-only.
297
298=head2 host
299
300The host value to set the "Host:" HTTP header to, if none is present already in
301the request. If not set (default) then Catalyst::Test will set this to
302localhost:80
303
304=head2 clear_host
305
306Unset the host attribute.
307
308=head2 has_host
309
310Do we have a value set for the host attribute
311
6bc86362 312=head2 $mech->get_ok($url, [ \%LWP_options ,] $desc)
313
314A wrapper around WWW::Mechanize's get(), with similar options, except the
315second argument needs to be a hash reference, not a hash. Returns true or
316false.
317
318=head2 $mech->title_is( $str [, $desc ] )
319
320Tells if the title of the page is the given string.
321
322 $mech->title_is( "Invoice Summary" );
323
324=head2 $mech->title_like( $regex [, $desc ] )
325
326Tells if the title of the page matches the given regex.
327
328 $mech->title_like( qr/Invoices for (.+)/
329
330=head2 $mech->title_unlike( $regex [, $desc ] )
331
d6fc3a22 332Tells if the title of the page does NOT match the given regex.
6bc86362 333
334 $mech->title_unlike( qr/Invoices for (.+)/
335
336=head2 $mech->content_is( $str [, $desc ] )
337
338Tells if the content of the page matches the given string
339
340=head2 $mech->content_contains( $str [, $desc ] )
341
342Tells if the content of the page contains I<$str>.
343
344=head2 $mech->content_lacks( $str [, $desc ] )
345
346Tells if the content of the page lacks I<$str>.
347
348=head2 $mech->content_like( $regex [, $desc ] )
349
350Tells if the content of the page matches I<$regex>.
351
352=head2 $mech->content_unlike( $regex [, $desc ] )
353
354Tells if the content of the page does NOT match I<$regex>.
355
356=head2 $mech->page_links_ok( [ $desc ] )
357
358Follow all links on the current page and test for HTTP status 200
359
360 $mech->page_links_ok('Check all links');
361
362=head2 $mech->page_links_content_like( $regex,[ $desc ] )
363
364Follow all links on the current page and test their contents for I<$regex>.
365
366 $mech->page_links_content_like( qr/foo/,
367 'Check all links contain "foo"' );
368
369=head2 $mech->page_links_content_unlike( $regex,[ $desc ] )
370
371Follow all links on the current page and test their contents do not
372contain the specified regex.
373
374 $mech->page_links_content_unlike(qr/Restricted/,
375 'Check all links do not contain Restricted');
376
377=head2 $mech->links_ok( $links [, $desc ] )
378
379Check the current page for specified links and test for HTTP status
380200. The links may be specified as a reference to an array containing
381L<WWW::Mechanize::Link> objects, an array of URLs, or a scalar URL
382name.
383
384 my @links = $mech->find_all_links( url_regex => qr/cnn\.com$/ );
385 $mech->links_ok( \@links, 'Check all links for cnn.com' );
386
387 my @links = qw( index.html search.html about.html );
388 $mech->links_ok( \@links, 'Check main links' );
389
390 $mech->links_ok( 'index.html', 'Check link to index' );
391
392=head2 $mech->link_status_is( $links, $status [, $desc ] )
393
394Check the current page for specified links and test for HTTP status
395passed. The links may be specified as a reference to an array
396containing L<WWW::Mechanize::Link> objects, an array of URLs, or a
397scalar URL name.
398
399 my @links = $mech->links();
400 $mech->link_status_is( \@links, 403,
401 'Check all links are restricted' );
402
403=head2 $mech->link_status_isnt( $links, $status [, $desc ] )
404
405Check the current page for specified links and test for HTTP status
406passed. The links may be specified as a reference to an array
407containing L<WWW::Mechanize::Link> objects, an array of URLs, or a
408scalar URL name.
409
410 my @links = $mech->links();
411 $mech->link_status_isnt( \@links, 404,
412 'Check all links are not 404' );
413
414=head2 $mech->link_content_like( $links, $regex [, $desc ] )
415
416Check the current page for specified links and test the content of
417each against I<$regex>. The links may be specified as a reference to
418an array containing L<WWW::Mechanize::Link> objects, an array of URLs,
419or a scalar URL name.
420
421 my @links = $mech->links();
422 $mech->link_content_like( \@links, qr/Restricted/,
423 'Check all links are restricted' );
424
425=head2 $mech->link_content_unlike( $links, $regex [, $desc ] )
426
427Check the current page for specified links and test the content of each
428does not match I<$regex>. The links may be specified as a reference to
429an array containing L<WWW::Mechanize::Link> objects, an array of URLs,
430or a scalar URL name.
431
432 my @links = $mech->links();
433 $mech->link_content_like( \@links, qr/Restricted/,
434 'Check all links are restricted' );
435
436=head2 follow_link_ok( \%parms [, $comment] )
437
438Makes a C<follow_link()> call and executes tests on the results.
439The link must be found, and then followed successfully. Otherwise,
440this test fails.
441
d6fc3a22 442I<%parms> is a hashref containing the params to pass to C<follow_link()>.
443Note that the params to C<follow_link()> are a hash whereas the parms to
6bc86362 444this function are a hashref. You have to call this function like:
445
446 $agent->follow_like_ok( {n=>3}, "looking for 3rd link" );
447
448As with other test functions, C<$comment> is optional. If it is supplied
449then it will display when running the test harness in verbose mode.
450
451Returns true value if the specified link was found and followed
452successfully. The HTTP::Response object returned by follow_link()
453is not available.
454
455=head1 SEE ALSO
456
457Related modules which may be of interest: L<Catalyst>,
458L<Test::WWW::Mechanize>, L<WWW::Mechanize>.
459
460=head1 AUTHOR
461
f21ad406 462Ash Berlin C<< <ash@cpan.org> >> (current maintiner)
254eca41 463
f21ad406 464Original Author: Leon Brocard, C<< <acme@astray.com> >>
6bc86362 465
466=head1 COPYRIGHT
467
254eca41 468Copyright (C) 2005-8, Leon Brocard
6bc86362 469
470=head1 LICENSE
471
472This module is free software; you can redistribute it or modify it
473under the same terms as Perl itself.
474