[Catalyst] Testing controller which require login.

Peter Edwards peter at dragonstaff.com
Wed May 13 09:32:13 GMT 2009


2009/5/13 Louis Erickson <lerickson at rdwarf.net>
>
> I haven't been able to get a test user logging in and out yet, though.
>
> I have several sections of my application that redirect the user away if
> they are not logged in.  I have another that returns a 404 if they don't
> have access, so prying users can't find it (as easily).
>
> My tests are currently based on the stubs generated by myapp_create,
> and use Catalyst::Test.  I could move them to WWW::Mechanize::Catalyst if
> it would help.
>
> I can't tell if Catalyst::Test supports cookies.  Does it?  Or do I need
> to use WWW::Mechanize::Catalyst for that?
>
> I've read some suggestions on how to go about this, and found a couple.
>
> 1> Use WWW::Mechanize::Catalyst to log the user in
> 2> Use the mock user in config to set a user who is logged in.
>
> The problem with really logging the user in is that the login is a set of
> site-wide cookies generated by a Single Sign On system.  That system may
> or may not have a test instance running.  If it does, cookies stored from
> it probably won't work with cookies from 'localhost' like the local test
> app wants.
>

The approach we used a year or two back was
1. In your application config file MyApp/conf/myapp.pl  set up multiple
authentication realms

  authentication =3D> {
      default_realm =3D> 'members',
      realms =3D> {
         members =3D> {
            credential =3D> {
               class =3D> 'Password',
               password_field =3D> 'password',
               password_type =3D> 'crypted',
            },
            store =3D> {
               ...
            },

         },
         debug =3D> {
            credential =3D> {
               class =3D> 'Password',
               password_field =3D> 'password',
               password_type =3D> 'clear',
            },
            store =3D> {
               class =3D> 'Minimal',
               users =3D> {
                  test =3D> {
                    login =3D> 'user',
                    password =3D> 'pass',
                    roles =3D> [qw/ user /],
                  },
                  admin =3D> {
                    login =3D> 'admin',
                    password =3D> 'pass',
                    roles =3D> [qw/ admin /],
                  },
               },
            },
         },

2. In root controller that checks authentication, have a case that checks
for environment variables DEBUG_REALM, DEBUG_USER, DEBUG_PASS and if set use
them to perform $c->authenticate() manually rather than redirecting to the
login screen

sub auto : Private {
    my ($self, $c) =3D @_;

    # Allow unauthenticated users to reach the login page.
    if ($c->action eq $c->controller('Web')->action_for('login')) {
        return 1;
    }

    # allow access during interactive debugging or from test scripts
    if ( $ENV{DEBUG_USER} ) {
      my $realm =3D $ENV{DEBUG_REALM}||'default';

      if ( $realm eq 'debug' ) # auth against built in debug users in
configuration
      {
        $c->authenticate({
          username =3D> ($ENV{DEBUG_USER}||'test'), # 'username' needed
for 'debug' realm Minimal authentication
          password =3D> ($ENV{DEBUG_PASS}||'pass'),
          }, $realm )
       || die "cannot perform debug authentication";
      }
      else # auth against normal database backend
      {
        $c->authenticate({
          login    =3D> ($ENV{DEBUG_USER}||'test'), # 'login' needed for
default 'members' realm
          password =3D> ($ENV{DEBUG_PASS}||'pass'),
          }, $realm )
       || die "cannot perform env authentication";

      }

    }

    if (!$c->user_exists) {

        $c->response->redirect($c->uri_for('/web/login/' .
$c->request->action));
        # Return 0 to cancel 'post-auto' processing and prevent use of
application
        return 0;
    }

    # resend cookie to extend its life
    $c->session_expires;

return 1; }
# normal interactive login point

sub login :Local {
    my ($self, $c, @path) =3D @_;
    my $username =3D $c->request->params->{username} || "";
    my $password =3D $c->request->params->{password} || "";

    if ($username && $password) {
        if ($c->authenticate({ login =3D> $username, password =3D> $passwor=
d }) ) {
    ...


3. Add a couple of helper scripts that run the test web server with
different auth realms for debugging/testing

script/myapp_server_with_auth_envvars.pl

#!/bin/sh
# run test server with auto auth
DEBUG_USER=3Dcust DEBUG_PASS=3Dcust DEBUG_REALM=3Dmembers perl -d
script/myapp_server.pl -k

script/myapp_server_with_debugrealm_auth_envvars.pl

#!/bin/sh
# run test server with debug auth
DEBUG_USER=3Dtest DEBUG_PASS=3Dpass DEBUG_REALM=3Ddebug perl -d
script/myapp_server.pl -k


4. Basic testing using Catalyst::Test

use strict;
use warnings;
use Test::More tests =3D> 2;

BEGIN { use_ok 'Catalyst::Test', 'MyApp' }

ok( request('/web/login')->is_success, 'Request should succeed' );
...


5. Web testing
Set up a test database/fixture with known data, start up web server using
debug auth realm via script/myapp_server_with_auth_envvars.pl and then use
Test::WWW::Mechanize::Catalyst to exercise your application

use strict;
use warnings;
use Test::More;

eval "use Test::WWW::Mechanize::Catalyst 'MyApp'";
plan $@
    ? ( skip_all =3D> 'Test::WWW::Mechanize::Catalyst required' )
    : ( tests =3D> 36 );

ok( my $mech =3D Test::WWW::Mechanize::Catalyst->new, 'Created mech object'=
 );

# logout screen redirects to login screen
$mech->get_ok( 'http://localhost/web/logout', 'logout' );
$mech->content_contains('Log on using', 'logout redirects to login screen');

diag "check login authentication is required to access extranet screens";
#
# turn off automatic redirect follow so we can check response code

$mech->requests_redirectable([]);# 1. check redirect code and location

$mech->get( 'http://localhost/web/extranet/list/booking' );
is($mech->response->headers->{status}, 302, 'unauthed user is
redirected away from page requiring auth');
like($mech->response->headers->{location}, qw|/web/login/|, 'redirect
is to login page');

# 2. check all protected paths
for (qw|
    index
    home
    booking
    ...

 |)
{
  my $path =3D '/web/extranet/'.$_;
  $mech->get( 'http://localhost/'.$path);
  is($mech->response->headers->{status}, 302, 'unauth redirect for '.$path);
}

# allow automatic redirect again
$mech->requests_redirectable([qw/ GET HEAD POST /]);

# get login screen
$mech->get_ok( 'http://localhost/web/login', 'get login' );
$mech->content_contains('Log on using', 'login contains Log on using');
# login
$mech->submit_form(
  fields =3D> {
      username =3D> 'cust',
      password =3D> 'cust',
  },
);
# check logged in successfully
$mech->content_contains('Welcome to the', 'successfully logged in screen');

diag "screen tests";
$mech->get_ok( 'http://localhost/web/extranet/home', 'get home' );
$mech->content_contains('Welcome to the', 'home contains welcome');

$mech->get_ok( 'http://localhost/web/extranet/list/booking', 'list/booking'=
 );
$mech->content_contains('25843011', 'booking list contains booking 25843011=
');

...


I hope that helps and doesn't get too mangled by the mailer.

Regards, Peter
http://perl.dragonstaff.co.uk
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.scsys.co.uk/pipermail/catalyst/attachments/20090513/325c6=
a52/attachment.htm


More information about the Catalyst mailing list