File Coverage

blib/lib/Dancer2/Plugin/Auth/Extensible/Test.pm
Criterion Covered Total %
statement 673 674 99.8
branch 109 210 51.9
condition 4 7 57.1
subroutine 28 29 96.5
pod 2 4 50.0
total 816 924 88.3


line stmt bran cond sub pod time code
1             package Dancer2::Plugin::Auth::Extensible::Test;
2              
3             our $VERSION = '0.708';
4              
5             =head1 NAME
6              
7             Dancer2::Plugin::Auth::Extensible::Test - test suite for Auth::Extensible plugin
8              
9             =cut
10              
11 4     4   568842 use warnings;
  4         20  
  4         133  
12 4     4   21 use strict;
  4         7  
  4         92  
13              
14 4     4   18 use Carp qw(croak);
  4         7  
  4         177  
15 4     4   20 use Test::More;
  4         8  
  4         19  
16 4     4   2942 use Test::Deep;
  4         26642  
  4         24  
17 4     4   2707 use Test::MockDateTime;
  4         1642445  
  4         205  
18 4     4   1529 use Plack::Test;
  4         1809  
  4         173  
19 4     4   1640 use HTTP::Cookies;
  4         29432  
  4         133  
20 4     4   1569 use HTTP::Request::Common qw(GET POST);
  4         64598  
  4         303  
21 4     4   1767 use YAML ();
  4         23026  
  4         31380  
22              
23             =head1 DESCRIPTION
24              
25             Test suite for L<Dancer2::Plugin::Auth::Extensible> which can also be used
26             by external providers. If you have written your own provider then you really
27             want to use this since it should make sure your provider conforms as
28             L<Dancer2::Plugin::Auth::Extensible> expects it to. It will also save you
29             writing piles of tests yourself.
30              
31             =head1 FUNCTIONS
32              
33             =head2 runtests $psgi_app
34              
35             This is the way to test your provider.
36              
37             =head2 testme
38              
39             This method no longer runs any tests but exists purely to force providers
40             trying to use the old tests to fail.
41              
42             =cut
43              
44             my $jar = HTTP::Cookies->new();
45              
46             my %dispatch = (
47             authenticate_user => \&_authenticate_user,
48             create_user => \&_create_user,
49             get_user_details => \&_get_user_details,
50             login_logout => \&_login_logout,
51             logged_in_user => \&_logged_in_user,
52             logged_in_user_lastlogin => \&_logged_in_user_lastlogin,
53             logged_in_user_password_expired => \&_logged_in_user_password_expired,
54             password_reset => \&_password_reset,
55             require_login => \&_require_login,
56             roles => \&_roles,
57             update_current_user => \&_update_current_user,
58             update_user => \&_update_user,
59             user_password => \&_user_password,
60             );
61              
62             # Provider methods needed by plugin tests.
63             # These are assumed to be correct. If they are not then some provider tests
64             # should fail and we can fixup later.
65             my %dependencies = (
66             create_user => [ 'get_user_details', 'create_user', 'set_user_details', ],
67             get_user_details => ['get_user_details'],
68             logged_in_user => ['get_user_details'],
69             logged_in_user_lastlogin => ['create_user','record_lastlogin'],
70             logged_in_user_password_expired =>
71             [ 'get_user_details', 'password_expired' ],
72             password_reset => ['get_user_by_code', 'set_user_details'],
73             require_login => ['get_user_details'],
74             roles => ['get_user_roles' ],
75             update_current_user => ['set_user_details'],
76             update_user => ['set_user_details'],
77             user_password =>
78             [ 'get_user_by_code', 'authenticate_user', 'set_user_details' ],
79             );
80              
81             my ( $test, $trap );
82              
83             sub testme {
84 0     0 1 0 BAIL_OUT "Please upgrade your provider to the latest version. Dancer2::Plugin::Auth::Extensible no longer supports the old \"testme\" tests.";
85             }
86              
87             # so test can check
88             my @provider_can;
89              
90             sub runtests {
91 2     2 1 8847 my $app = shift;
92              
93 2         21 $test = Plack::Test->create($app);
94 2         17893 $trap = TestApp->dancer_app->logger_engine->trapper;
95              
96 2         153 my $res = get('/provider_can');
97 2 50       12 BAIL_OUT "Unable to determine what methods the provider supports"
98             unless $res->is_success;
99              
100 2         28 my $ret = YAML::Load $res->content;
101              
102 2 50       21798 BAIL_OUT "Unexpected response to /provider_can"
103             unless ref($ret) eq 'ARRAY';
104              
105 2         9 @provider_can = @$ret;
106              
107 2   33     37 my @to_test = ($ENV{D2PAE_TEST_ONLY}) || sort keys %dispatch;
108              
109 2         8 foreach my $test ( @to_test ) {
110 26         70822 my @missing;
111 26 100       59 foreach my $dep ( @{ $dependencies{$test} || [] } ) {
  26         159  
112 36 100       91 push @missing, $dep if !grep { $_ eq $dep } @provider_can;
  216         428  
113             }
114             SKIP: {
115 26 100       50 skip "Provider $test tests as provider is missing methods: "
  26         115  
116             . join( ", ", @missing ), 1
117             if @missing;
118              
119             # for safety in case one set of tests doesn't clean up carefully
120 19         125 $jar->clear;
121              
122 19         272 subtest "Plugin $test tests" => $dispatch{$test};
123             }
124             }
125             }
126              
127             sub get {
128 157     157 0 473 my $uri = shift;
129 157         916 my $req = GET "http://localhost$uri";
130 157         35677 $jar->add_cookie_header($req);
131 157         49525 my $res = $test->request($req);
132 157         2068743 $jar->extract_cookies($res);
133 157         61476 return $res;
134             }
135              
136             sub post {
137 136     136 0 739 my $uri = shift;
138 136   100     656 my $params = shift || [];
139 136         904 my $req = POST "http://localhost$uri", $params;
140 136         64396 $jar->add_cookie_header($req);
141 136         37154 my $res = $test->request($req);
142 136         1015681 $jar->extract_cookies($res);
143 136         44781 return $res;
144             }
145              
146             #------------------------------------------------------------------------------
147             #
148             # authenticate_user
149             #
150             #------------------------------------------------------------------------------
151              
152             sub _authenticate_user {
153 2     2   2488 my ($res, $data, $logs);
154              
155             # no args
156              
157 2         14 $trap->read;
158 2         176 $res = post('/authenticate_user');
159 2         11 ok $res->is_success, "/authenticate_user with no params is_success";
160 2         1116 cmp_deeply YAML::Load( $res->content ), [ 0, undef ],
161             "... and returns expected response";
162 2         14252 cmp_deeply $trap->read,
163             superbagof(
164             {
165             formatted => ignore(),
166             level => 'debug',
167             message => 'before_authenticate_user{"password":null,"realm":null,"username":null}'
168             },
169             {
170             formatted => ignore(),
171             level => 'debug',
172             message => 'after_authenticate_user{"errors":[],"password":null,"realm":null,"success":0,"username":null}'
173             }
174             ),
175             "... and we see expected hook output in logs.";
176              
177             # empty username and password
178              
179 2         18537 $res = post('/authenticate_user',[username=>'',password=>'']);
180 2         13 ok $res->is_success,
181             "/authenticate_user with empty username and password is_success";
182 2         1004 cmp_deeply YAML::Load( $res->content ), [ 0, undef ],
183             "... and returns expected response";
184 2         5071 cmp_deeply $trap->read,
185             superbagof(
186             {
187             formatted => ignore(),
188             level => 'debug',
189             message => 'before_authenticate_user{"password":"","realm":null,"username":""}'
190             },
191             {
192             formatted => ignore(),
193             level => 'debug',
194             message => 'after_authenticate_user{"errors":[],"password":"","realm":null,"success":0,"username":""}'
195             }
196             ),
197             "... and we see expected hook output in logs.";
198              
199             # good username, bad password and no realm
200              
201 2         7462 $res = post('/authenticate_user',[username=>'dave',password=>'badpwd']);
202 2         12 ok $res->is_success,
203             "/authenticate_user with user dave, bad password and no realm success";
204 2         990 cmp_deeply YAML::Load( $res->content ), [ 0, undef ],
205             "... and returns expected response";
206 2         4934 $logs = $trap->read;
207 2 50       141 cmp_deeply $logs,
208             superbagof(
209             {
210             formatted => ignore(),
211             level => 'debug',
212             message => 'before_authenticate_user{"password":"badpwd","realm":null,"username":"dave"}'
213             },
214             {
215             formatted => ignore(),
216             level => 'debug',
217             message => re(qr/Attempting.+dave.+realm config2/)
218             },
219             {
220             formatted => ignore(),
221             level => 'debug',
222             message => re(qr/Attempting.+dave.+realm config3/)
223             },
224             {
225             formatted => ignore(),
226             level => 'debug',
227             message => re(qr/Attempting.+dave.+realm config1/)
228             },
229             {
230             formatted => ignore(),
231             level => 'debug',
232             message => 'after_authenticate_user{"errors":[],"password":"badpwd","realm":null,"success":0,"username":"dave"}'
233             }
234             ),
235             "... and we see expected hook output in logs and realms checked."
236             or diag explain $logs;
237              
238             # good username, good password but wrong realm
239              
240 2         53980 $res = post( '/authenticate_user',
241             [ username => 'dave', password => 'beer', realm => 'config2' ] );
242 2         14 ok $res->is_success,
243             "/authenticate_user with user dave, good password but wrong realm success";
244 2         1070 cmp_deeply YAML::Load( $res->content ), [ 0, undef ],
245             "... and returns expected response";
246              
247 2         5212 $logs = $trap->read;
248 2 50       154 cmp_deeply $logs,
249             superbagof(
250             {
251             formatted => ignore(),
252             level => 'debug',
253             message => 'before_authenticate_user{"password":"beer","realm":"config2","username":"dave"}'
254             },
255             {
256             formatted => ignore(),
257             level => 'debug',
258             message => re(qr/Attempting.+dave.+realm config2/)
259             },
260             {
261             formatted => ignore(),
262             level => 'debug',
263             message => 'after_authenticate_user{"errors":[],"password":"beer","realm":null,"success":0,"username":"dave"}'
264             }
265             ),
266             "... and we see expected hook output in logs and realm config2 checked"
267             or diag explain $logs;
268              
269 2 50       18811 cmp_deeply $logs,
270             noneof(
271             {
272             formatted => ignore(),
273             level => 'debug',
274             message => re(qr/Attempting.+dave.+realm config1/)
275             },
276             {
277             formatted => ignore(),
278             level => 'debug',
279             message => re(qr/Attempting.+dave.+realm config3/)
280             },
281             ),
282             "... and the other realms were not checked."
283             or diag explain $logs;
284              
285             # good username, good password and good realm
286              
287 2         15261 $res = post( '/authenticate_user',
288             [ username => 'dave', password => 'beer', realm => 'config1' ] );
289 2         15 ok $res->is_success,
290             "/authenticate_user with user dave, good password and good realm success";
291 2         1053 cmp_deeply YAML::Load( $res->content ), [ 1, "config1" ],
292             "... and returns expected response";
293              
294 2         5104 $logs = $trap->read;
295 2 50       154 cmp_deeply $logs,
296             superbagof(
297             {
298             formatted => ignore(),
299             level => 'debug',
300             message => 'before_authenticate_user{"password":"beer","realm":"config1","username":"dave"}'
301             },
302             {
303             formatted => ignore(),
304             level => 'debug',
305             message => re(qr/Attempting.+dave.+realm config1/)
306             },
307             {
308             formatted => ignore(),
309             level => 'debug',
310             message => re(qr/config1 accepted user dave/),
311             },
312             {
313             formatted => ignore(),
314             level => 'debug',
315             message => 'after_authenticate_user{"errors":[],"password":"beer","realm":"config1","success":1,"username":"dave"}'
316             }
317             ),
318             "... and we see expected hook output in logs and only one realm checked"
319             or diag explain $logs;
320              
321 2 50       24570 cmp_deeply $logs,
322             noneof(
323             {
324             formatted => ignore(),
325             level => 'debug',
326             message => re(qr/Attempting.+dave.+realm config2/)
327             },
328             {
329             formatted => ignore(),
330             level => 'debug',
331             message => re(qr/Attempting.+dave.+realm config3/)
332             },
333             ),
334             "... and the other realms were not checked."
335             or diag explain $logs;
336              
337             # good username, good password and no realm
338              
339 2         18974 $res = post( '/authenticate_user',
340             [ username => 'dave', password => 'beer' ] );
341 2         15 ok $res->is_success,
342             "/authenticate_user with user dave, good password and no realm success";
343 2         1065 cmp_deeply YAML::Load( $res->content ), [ 1, "config1" ],
344             "... and returns expected response";
345              
346 2         5171 $logs = $trap->read;
347 2 50       158 cmp_deeply $logs,
348             superbagof(
349             {
350             formatted => ignore(),
351             level => 'debug',
352             message => 'before_authenticate_user{"password":"beer","realm":null,"username":"dave"}'
353             },
354             {
355             formatted => ignore(),
356             level => 'debug',
357             message => re(qr/Attempting.+dave.+realm config2/)
358             },
359             {
360             formatted => ignore(),
361             level => 'debug',
362             message => re(qr/Attempting.+dave.+realm config3/)
363             },
364             {
365             formatted => ignore(),
366             level => 'debug',
367             message => re(qr/Attempting.+dave.+realm config1/)
368             },
369             {
370             formatted => ignore(),
371             level => 'debug',
372             message => re(qr/config1 accepted user dave/),
373             },
374             {
375             formatted => ignore(),
376             level => 'debug',
377             message => 'after_authenticate_user{"errors":[],"password":"beer","realm":"config1","success":1,"username":"dave"}'
378             }
379             ),
380             "... and we see expected hook output in logs and 3 realms checked."
381             or diag explain $logs;
382              
383             # good username, good password and no realm using 2nd realm by priority
384              
385 2         57900 $res = post( '/authenticate_user',
386             [ username => 'bananarepublic', password => 'whatever' ] );
387 2         15 ok $res->is_success,
388             "/authenticate_user with user bananarepublic, good password and no realm success";
389 2         1067 cmp_deeply YAML::Load( $res->content ), [ 1, "config3" ],
390             "... and returns expected response";
391              
392 2         5157 $logs = $trap->read;
393 2 50       165 cmp_deeply $logs,
394             superbagof(
395             {
396             formatted => ignore(),
397             level => 'debug',
398             message => 'before_authenticate_user{"password":"whatever","realm":null,"username":"bananarepublic"}'
399             },
400             {
401             formatted => ignore(),
402             level => 'debug',
403             message => re(qr/Attempting.+bananarepublic.+realm config2/)
404             },
405             {
406             formatted => ignore(),
407             level => 'debug',
408             message => re(qr/Attempting.+bananarepublic.+realm config3/)
409             },
410             {
411             formatted => ignore(),
412             level => 'debug',
413             message => re(qr/config3 accepted user bananarepublic/),
414             },
415             {
416             formatted => ignore(),
417             level => 'debug',
418             message => 'after_authenticate_user{"errors":[],"password":"whatever","realm":"config3","success":1,"username":"bananarepublic"}'
419             }
420             ),
421             "... and we see expected hook output in logs and 2 realms checked"
422             or diag explain $logs;
423              
424 2 50       39718 cmp_deeply $logs,
425             noneof(
426             {
427             formatted => ignore(),
428             level => 'debug',
429             message => re(qr/Attempting.+bananarepublic.+realm config1/)
430             },
431             ),
432             "... and we don't see realm config1 checked."
433             or diag explain $logs;
434              
435             # quick pairwise for coverage
436 2         11133 foreach my $username ( undef, +{}, '', 'username' ) {
437 8         16441 foreach my $password ( undef, +{}, '', 'password' ) {
438 32         60853 $res = post( '/authenticate_user',
439             [ username => $username, password => $password ] );
440 32         199 ok $res->is_success, "/authenticate_user with user dave, bad password and no realm success";
441 32         20815 cmp_deeply YAML::Load( $res->content ), [ 0, undef ],
442             "... and returns expected response";
443             }
444             }
445             }
446              
447             #------------------------------------------------------------------------------
448             #
449             # create_user
450             #
451             #------------------------------------------------------------------------------
452              
453             sub _create_user {
454 1     1   949 my ( $res, $logs );
455              
456             # create user with no args should die since we have > 1 realm
457              
458 1         7 $trap->read;
459              
460 1         93 $res = post('/create_user');
461 1         5 is $res->code, 500,
462             "/create_user with no params is 500 due to > 1 realm.";
463              
464 1         495 $logs = $trap->read;
465 1         72 cmp_deeply $logs,
466             [
467             {
468             formatted => ignore(),
469             level => 'error',
470             message => re(
471             qr/Realm must be specified when more than one realm configured/
472             ),
473             }
474             ],
475             "... and error about needing realm was logged.";
476              
477             # create user with no password
478              
479 1         2658 $res = post( "/create_user",
480             [ username => 'createusernopw', realm => 'config1' ] );
481 1         9 ok $res->is_success, "/create_user with no password is_success";
482              
483 1         546 for my $realm (qw/config1 config2/) {
484              
485             # create a user
486              
487 2         10 my $data = [
488             username => 'newuser',
489             password => "pish_$realm",
490             realm => $realm,
491             ];
492              
493 2         8 $res = post( "/create_user", $data );
494 2 50       11 ok $res->is_success, "/create_user newuser in realm $realm is success"
495             or diag explain $trap->read;
496 2         1182 is $res->content, 1, "... and response content shows create success";
497              
498 2         705 $logs = $trap->read;
499 2         148 cmp_deeply $logs,
500             superbagof(
501             {
502             formatted => ignore(),
503             level => 'debug',
504             message => qq(before_create_user{"password":"pish_$realm","realm":"$realm","username":"newuser"}),
505             },
506             {
507             formatted => ignore(),
508             level => 'debug',
509             message => 'after_create_user,newuser,1,no',
510             }
511             ),
512             "... and we see expected before/after hook logs.";
513              
514             # try creating same user a second time
515              
516 2         8291 $res = post( "/create_user", $data );
517 2 50       11 ok $res->is_success,
518             "/create_user newuser *again* in realm $realm is success"
519             or diag explain $trap->read;
520 2         4828 is $res->content, 0, "... and response content shows create failed";
521              
522 2         791 $logs = $trap->read;
523 2 50       158 cmp_deeply $logs,
524             superbagof(
525             {
526             formatted => ignore(),
527             level => 'debug',
528             message => qq(before_create_user{"password":"pish_$realm","realm":"$realm","username":"newuser"}),
529             },
530             {
531             formatted => ignore(),
532             level => 'error',
533             message => re(qr/$realm provider threw error/),
534             },
535             {
536             formatted => ignore(),
537             level => 'debug',
538             message => re(qr/after_create_user,newuser,0,yes/),
539             }
540             ),
541             "... and we see expected before/after hook logs."
542             or diag explain $logs;
543              
544             # Then try logging in with that user
545              
546 2         21347 $trap->read; # clear logs
547              
548 2         262 $res = post( '/login', $data );
549              
550 2 50       9 is( $res->code, 302, 'Login with newly created user succeeds' )
551             or diag explain $trap->read;
552              
553 2         992 my $logs = $trap->read;
554 2 50       141 cmp_deeply $logs,
555             superbagof(
556             {
557             formatted => ignore(),
558             level => 'debug',
559             message => "$realm accepted user newuser"
560             }
561             ),
562             "... and we see expected message in logs."
563             or diag explain $res;
564              
565 2         5677 is get('/loggedin')->content, "You are logged in",
566             "... and checking /loggedin route shows we are logged in";
567              
568 2         1180 get('/logout');
569             }
570              
571             # create user with `email_welcome` so we can test reset code
572              
573 1         4 $Dancer2::Plugin::Auth::Extensible::Test::App::data = undef;
574              
575 1         5 $res = post(
576             "/create_user",
577             [
578             username => 'newuserwithcode',
579             realm => 'config1',
580             email_welcome => 1,
581             ]
582             );
583              
584 1 50       7 is $res->code, 200, "/create_user with welcome_send=>1 response is 200"
585             or diag explain $trap->read;
586              
587             # the args passed to 'welcome_send' sub
588 1         478 my $args = $Dancer2::Plugin::Auth::Extensible::Test::App::data;
589 1         10 like $args->{code}, qr/^\w{32}$/,
590             "... and we have a reset code in the email";
591             }
592              
593             #------------------------------------------------------------------------------
594             #
595             # get_user_details
596             #
597             #------------------------------------------------------------------------------
598              
599             sub _get_user_details {
600 2     2   1828 my ( $logs, $res );
601              
602             # no args
603              
604 2         8 $res = post('/get_user_details');
605 2         12 ok $res->is_success, "/get_user_details with no params is_success";
606 2         898 is $res->content, 0, "... and no user was returned.";
607              
608             # unknown user
609              
610 2         792 $trap->read;
611              
612 2         182 $res = post( '/get_user_details', [ username => 'NoSuchUser' ] );
613 2         14 ok $res->is_success, "/get_user_details with unknown user is_success";
614 2         976 is $res->content, 0, "... and no user was returned.";
615              
616 2         759 $logs = $trap->read;
617 2 50       137 cmp_deeply $logs, superbagof(
618             {
619             formatted => ignore(),
620             level => 'debug',
621             message => 'Attempting to find user NoSuchUser in realm config2',
622             },
623             {
624             formatted => ignore(),
625             level => 'debug',
626             message => 'Attempting to find user NoSuchUser in realm config3',
627             },
628             {
629             formatted => ignore(),
630             level => 'debug',
631             message => 'Attempting to find user NoSuchUser in realm config1',
632             },
633             ), "... and we see logs we expect."
634             or diag explain $logs;
635              
636             # known user but wrong realm
637              
638 2         15959 $trap->read;
639              
640 2         146 $res =
641             post( '/get_user_details', [ username => 'dave', realm => 'config2' ] );
642 2         13 ok $res->is_success, "/get_user_details dave config2 is_success";
643 2         989 is $res->content, 0, "... and no user was returned (wrong realm).";
644              
645 2         746 $logs = $trap->read;
646 2 50       146 cmp_deeply $logs, superbagof(
647             {
648             formatted => ignore(),
649             level => 'debug',
650             message => 'Attempting to find user dave in realm config2',
651             },
652             ), "... and we see logs we expect" or diag explain $logs;
653              
654 2 50       3553 cmp_deeply $logs, noneof(
655             {
656             formatted => ignore(),
657             level => 'debug',
658             message => 'Attempting to find user dave in realm config3',
659             },
660             {
661             formatted => ignore(),
662             level => 'debug',
663             message => 'Attempting to find user dave in realm config1',
664             },
665             ), "... and none of the ones we don't expect." or diag explain $logs;
666              
667             # known user unspecified realm
668              
669 2         7166 $trap->read;
670              
671 2         152 $res =
672             post( '/get_user_details', [ username => 'dave' ] );
673 2         13 ok $res->is_success, "/get_user_details dave in any realm is_success";
674 2         1039 like $res->content, qr/David Precious/,
675             "... and correct user was returned.";
676              
677             # known user correct realm
678              
679 2         739 $trap->read;
680              
681 2         138 $res =
682             post( '/get_user_details', [ username => 'dave', realm => 'config1' ] );
683 2         14 ok $res->is_success, "/get_user_details dave in config1 is_success";
684 2         1045 like $res->content, qr/David Precious/,
685             "... and correct user was returned.";
686              
687             };
688              
689             #------------------------------------------------------------------------------
690             #
691             # login_logout
692             #
693             # also includes some auth_provider tests
694             #
695             #------------------------------------------------------------------------------
696              
697             sub _login_logout {
698 2     2   2120 my ( $data, $res, $logs );
699              
700             # auth_provider with no args
701              
702 2         17 $trap->read;
703 2         233 $res = post('/auth_provider');
704 2         10 is $res->code, 500, "auth_provider with no args dies";
705 2         1003 $logs = $trap->read;
706 2 50       180 cmp_deeply $logs, superbagof(
707             { formatted => ignore(),
708             level => 'error',
709             message => re(qr/auth_provider needs realm or/),
710             }
711             ), "... and correct error message is seen in logs." or diag explain $logs;
712              
713             # auth_provider with non-existant realm
714              
715 2         3831 $trap->read;
716 2         124 $res = post('/auth_provider', [realm => 'NoSuchRealm']);
717 2         16 is $res->code, 500, "auth_provider with non-existant realm dies";
718 2         14531 $logs = $trap->read;
719 2 50       172 cmp_deeply $logs, superbagof(
720             { formatted => ignore(),
721             level => 'error',
722             message => re(qr/Invalid realm NoSuchRealm/),
723             }
724             ), "... and correct error message is seen in logs." or diag explain $logs;
725              
726             # auth_provider with good realm
727              
728 2         3979 $res = post('/auth_provider', [realm => 'config1']);
729 2 50       16 ok $res->is_success, "auth_provider with good realm lives"
730             or diag explain $trap->read;
731              
732             # Check that login route doesn't match any request string with '/login'.
733              
734 2         894 $trap->read;
735 2         130 $res = get('/foo/login');
736 2 50       11 is $res->code, 404, "'/foo/login' URL not matched by login route regex."
737             or diag explain $trap->read;
738              
739             # Now, without being logged in, check we can access the index page,
740             # but not stuff we need to be logged in for:
741              
742 2         938 $res = get('/');
743 2         13 ok $res->is_success, "Index always accessible - GET / success";
744 2         840 is $res->content, 'Index always accessible',
745             "...and we got expected content.";
746              
747             # check session_data when not logged in
748              
749 2         723 $res = get('/session_data');
750 2         38 ok $res->is_success, "/session_data is_success";
751 2         961 $data = YAML::Load $res->content;
752             ok !defined $data->{logged_in_user},
753 2         1180 "... and logged_in_user is not set in the session";
754             ok !defined $data->{logged_in_user_realm},
755 2         702 "... and logged_in_user_realm is not set in the session.";
756              
757             # get /login
758              
759 2         674 $res = get('/login');
760 2         13 ok $res->is_success, "GET /login is_success";
761 2         1016 like $res->content, qr/input.+name="password"/,
762             "... and we have the login page.";
763              
764             # login page has password reset section (or not)
765 2 100       695 if ( grep { $_ eq 'get_user_by_code' } @provider_can ) {
  12         30  
766 1         5 like $res->content,
767             qr/Enter your username to obtain an email to reset your password/,
768             "... which has password reset option (reset_password_handler=>1).";
769             }
770             else {
771 1         5 unlike $res->content,
772             qr/Enter your username to obtain an email to reset your password/,
773             "... which has *no* password reset option (reset_password_handler=>0)";
774              
775             # code coverage 'Reset password code submitted?' section of default
776             # get /login route.
777 1         331 $res = get('/login/12345678901234567890123456789012');
778 1         5 ok $res->is_success, "... and try a get /login/<code> is_success";
779             }
780              
781             # post empty /login
782              
783 2         717 $res = post('/login');
784 2         13 ok $res->is_success, "POST /login is_success";
785 2         1179 like $res->content, qr/input.+name="password"/,
786             "... and we have the login page";
787 2         687 like $res->content, qr/LOGIN FAILED/,
788             "... and we see LOGIN FAILED";
789              
790             # check session_data again
791              
792 2         664 $res = get('/session_data');
793 2         12 ok $res->is_success, "/session_data is_success";
794 2         1082 $data = YAML::Load $res->content;
795             ok !defined $data->{logged_in_user},
796 2         1171 "... and logged_in_user is not set in the session";
797             ok !defined $data->{logged_in_user_realm},
798 2         718 "... and logged_in_user_realm is not set in the session.";
799              
800             # post good /login
801              
802 2         699 $res = post( '/login', [ username => 'dave', password => 'beer' ] );
803 2         14 ok $res->is_redirect, "POST /login with good username/password is_redirect";
804 2         1036 is $res->header('location'), 'http://localhost/',
805             "... and redirect location is correct.";
806              
807             # check session_data again
808              
809 2         727 $res = get('/session_data');
810 2         11 ok $res->is_success, "/session_data is_success";
811 2         969 $data = YAML::Load $res->content;
812 2         3134 is $data->{logged_in_user}, 'dave',
813             "... and session logged_in_user is set to dave";
814 2         742 is $data->{logged_in_user_realm}, 'config1',
815             "... and session logged_in_user_realm is set to config1.";
816              
817             # get /login whilst already logged in
818              
819 2         680 $res = get('/login');
820 2 50       13 ok $res->is_redirect, "GET /login whilst logged in is redirected."
821             or diag explain $res;
822 2         1148 is $res->header('location'), 'http://localhost/',
823             "... and redirect location is correct.";
824              
825             # get /login whilst already logged in with return_url set
826              
827 2         687 $res = get('/login?return_url=/foo');
828 2         11 ok $res->is_redirect,
829             "GET /login whilst logged in with return_url set in query is redirected.";
830 2         917 is $res->header('location'), 'http://localhost/foo',
831             "... and redirect location is correct.";
832              
833             # auth_provider with no realm but user is logged in
834              
835 2         751 $res = post('/auth_provider');
836 2 50       11 ok $res->is_success, "auth_provider with *no* realm lives"
837             or diag explain $trap->read;
838              
839             # get /logout
840              
841 2         837 $res = get('/logout');
842 2 50       11 ok $res->is_redirect, "GET /logout is_redirect" or diag explain $res;
843 2         934 is $res->header('location'), 'http://localhost/',
844             "... and redirect location is correct.";
845              
846             # check session_data again
847              
848 2         723 $res = get('/session_data');
849 2         13 ok $res->is_success, "/session_data is_success";
850 2         1061 $data = YAML::Load $res->content;
851             ok !defined $data->{logged_in_user},
852 2         1180 "... and logged_in_user is not set in the session";
853             ok !defined $data->{logged_in_user_realm},
854 2         720 "... and logged_in_user_realm is not set in the session.";
855              
856             # post good /login
857              
858 2         679 $res = post( '/login', [ username => 'dave', password => 'beer' ] );
859 2         14 ok $res->is_redirect, "POST /login with good username/password is_redirect";
860 2         1052 is $res->header('location'), 'http://localhost/',
861             "... and redirect location is correct.";
862              
863             # check session_data again
864              
865 2         712 $res = get('/session_data');
866 2         11 ok $res->is_success, "/session_data is_success";
867 2         881 $data = YAML::Load $res->content;
868 2 50       3140 is $data->{logged_in_user}, 'dave',
869             "... and session logged_in_user is set to dave" or diag explain $data;
870 2         742 is $data->{logged_in_user_realm}, 'config1',
871             "... and session logged_in_user_realm is set to config1.";
872              
873             # POST /logout with return_url
874              
875 2         721 $res = post('/logout', [ return_url => '/foo/bar' ] );
876 2 50       14 ok $res->is_redirect, "POST /logout with return_url /foo/bar is_redirect"
877             or diag explain $res;
878 2         924 is $res->header('location'), 'http://localhost/foo/bar',
879             "... and redirect location /foo/bar is correct.";
880              
881             # check session_data again
882              
883 2         713 $res = get('/session_data');
884 2         12 ok $res->is_success, "/session_data is_success";
885 2         922 $data = YAML::Load $res->content;
886             ok !defined $data->{logged_in_user},
887 2         1136 "... and logged_in_user is not set in the session";
888             ok !defined $data->{logged_in_user_realm},
889 2         699 "... and logged_in_user_realm is not set in the session.";
890              
891             # Now check we can log in as a user whose password is stored hashed:
892              
893             {
894 2         16 $trap->read; # clear logs
895              
896 2         194 my $res = post(
897             '/login',
898             {
899             username => 'hashedpassword',
900             password => 'password'
901             }
902             );
903              
904 2 50       13 is( $res->code, 302, 'Login as user with hashed password succeeds' )
905             or diag explain $trap->read;
906              
907 2         915 my $logs = $trap->read;
908 2         140 cmp_deeply $logs,
909             superbagof(
910             {
911             formatted => ignore(),
912             level => 'debug',
913             message => 'config2 accepted user hashedpassword'
914             }
915             ),
916             "... and we see expected message in logs.";
917              
918 2         5702 is get('/loggedin')->content, "You are logged in",
919             "... and checking /loggedin route shows we are logged in";
920             }
921              
922             # And that now we're logged in again, we can access protected pages
923              
924             {
925 2         706 $trap->read; # clear logs
  2         13  
926              
927 2         136 my $res = get('/loggedin');
928              
929 2 50       10 is( $res->code, 200, 'Can access /loggedin now we are logged in again' )
930             or diag explain $trap->read;
931             }
932              
933             # Check that the redirect URL can be set when logging in
934              
935             {
936 2         916 $trap->read; # clear logs
  2         12  
937              
938             # make sure we're logged out
939 2         134 get('/logout');
940              
941 2         15 my $res = post(
942             '/login',
943             {
944             username => 'dave',
945             password => 'beer',
946             return_url => '/foobar',
947             }
948             );
949              
950 2 50       15 is( $res->code, 302, 'Status code for login with return_url' )
951             or diag explain $trap->read;
952              
953 2         1095 is( $res->headers->header('Location'),
954             'http://localhost/foobar',
955             'Redirect after login to given return_url works' );
956              
957 2         715 my $logs = $trap->read;
958 2 50       146 cmp_deeply $logs,
959             superbagof(
960             {
961             formatted => ignore(),
962             level => 'debug',
963             message => 'config1 accepted user dave'
964             }
965             ),
966             "... and we see expected message in logs." or diag explain $logs;
967              
968 2         7700 is get('/loggedin')->content, "You are logged in",
969             "... and checking /loggedin route shows we are logged in";
970             }
971              
972             # Now, log out again
973              
974             {
975 2         908 $trap->read; # clear logs
  2         13  
976              
977 2         135 my $res = post('/logout');
978 2 50       10 is( $res->code, 302, 'Logging out returns 302' )
979             or diag explain $trap->read;
980              
981 2         932 is( $res->headers->header('Location'),
982             'http://localhost/',
983             '/logout redirected to / (exit_page) after logging out' );
984             }
985              
986             # /login/denied page
987              
988             {
989 2         995 my $res = get('/login/denied');
  2         723  
  2         9  
990 2         17 is $res->code, '403', "GET /login/denied results in a 403 denied code";
991 2         818 like $res->content, qr/Permission Denied/,
992             "... and we have Permission Denied text in page";
993             }
994             }
995              
996             #------------------------------------------------------------------------------
997             #
998             # logged_in_user
999             #
1000             #------------------------------------------------------------------------------
1001              
1002             sub _logged_in_user {
1003 2     2   1860 my ( $data, $res );
1004              
1005             # check logged_in_user when not logged in
1006              
1007 2         9 $res = get('/logged_in_user');
1008 2         12 ok $res->is_success, "/logged_in_user is_success";
1009 2         1032 $data = YAML::Load $res->content;
1010 2 50       1189 is $data, 'none', "... and there is no logged_in_user."
1011             or diag explain $data;
1012              
1013             # post empty /login
1014              
1015 2         726 $res = post('/login');
1016 2         11 ok $res->is_success, "POST /login is_success";
1017 2         1060 like $res->content, qr/input.+name="password"/,
1018             "... and we have the login page";
1019 2         721 like $res->content, qr/LOGIN FAILED/,
1020             "... and we see LOGIN FAILED";
1021              
1022             # check logged_in_user again
1023              
1024 2         670 $res = get('/logged_in_user');
1025 2         11 ok $res->is_success, "/logged_in_user is_success";
1026 2         885 $data = YAML::Load $res->content;
1027 2 50       1202 is $data, 'none', "... and there is no logged_in_user."
1028             or diag explain $data;
1029              
1030             # post good /login
1031              
1032 2         746 $res = post( '/login', [ username => 'dave', password => 'beer' ] );
1033 2         15 ok $res->is_redirect, "POST /login with good username/password is_redirect";
1034 2         944 is $res->header('location'), 'http://localhost/',
1035             "... and redirect location is correct.";
1036              
1037             # check logged_in_user again
1038              
1039 2         719 $res = get('/logged_in_user');
1040 2         11 ok $res->is_success, "/logged_in_user is_success";
1041 2         4418 $data = YAML::Load $res->content;
1042 2 50       115638 is $data->{name}, 'David Precious',
1043             "... and we see dave's name is David Precious." or diag explain $data;
1044              
1045             # check logged_in_user gets cached (coverage)
1046              
1047 2         888 $res = get('/logged_in_user_twice');
1048 2         11 ok $res->is_success, "/logged_in_user_twice is_success";
1049 2         1036 $data = YAML::Load $res->content;
1050 2 50       109153 is $data->{name}, 'David Precious',
1051             "... and we see dave's name is David Precious." or diag explain $data;
1052              
1053             # get /logout
1054              
1055 2         858 $res = get('/logout');
1056 2 50       11 ok $res->is_redirect, "GET /logout is_redirect" or diag explain $res;
1057 2         936 is $res->header('location'), 'http://localhost/',
1058             "... and redirect location is correct.";
1059              
1060             # check logged_in_user again
1061              
1062 2         698 $res = get('/logged_in_user');
1063 2         12 ok $res->is_success, "/logged_in_user is_success";
1064 2         907 $data = YAML::Load $res->content;
1065 2 50       1395 is $data, 'none', "... and there is no logged_in_user."
1066             or diag explain $data;
1067             }
1068              
1069             #------------------------------------------------------------------------------
1070             #
1071             # logged_in_user_lastlogin
1072             #
1073             #------------------------------------------------------------------------------
1074              
1075             sub _logged_in_user_lastlogin {
1076 1     1   890 my ( $res, $session );
1077              
1078             # create a new user for test so we are sure lastlogin has not been set
1079              
1080 1         6 $res = post(
1081             "/create_user",
1082             [
1083             username => 'lastlogin1',
1084             password => 'lastlogin2',
1085             realm => 'config1',
1086             ]
1087             );
1088 1         7 ok $res->is_success, "create_user lastlogin1 call is_success";
1089              
1090             # check the session for logged_in_user_lastlogin
1091              
1092 1         9913 $res = get('/session_data');
1093 1         5 ok $res->is_success, "get /session_data is_success";
1094 1         8867 $session = YAML::Load $res->content;
1095             ok !defined $session->{logged_in_user_lastlogin},
1096 1         603 "... and logged_in_user_lastlogin is not set in the session.";
1097              
1098             # we cannot reach require_login routes
1099              
1100 1         402 $res = get('/loggedin');
1101 1         9 ok $res->is_redirect, "GET /loggedin causes redirect";
1102 1         11851 is $res->header('location'),
1103             'http://localhost/login?return_url=%2Floggedin',
1104             "... and we're redirected to /login with return_url=/loggedin.";
1105              
1106             # login
1107              
1108 1         444 $res =
1109             post( '/login', [ username => 'lastlogin1', password => 'lastlogin2' ] );
1110 1         10 ok $res->is_redirect, "POST /login with with new user is_redirect";
1111 1         4422 is $res->header('location'), 'http://localhost/',
1112             "... and redirect location is correct.";
1113              
1114             # check we can reach restricted page
1115              
1116 1         1229 $res = get('/loggedin');
1117 1         6 ok $res->is_success, "GET /loggedin is_success now we're logged in";
1118 1         588 is $res->content, "You are logged in", "... and we can see page content.";
1119              
1120             # check the session for logged_in_user_lastlogin
1121              
1122 1         363 $res = get('/session_data');
1123 1         5 ok $res->is_success, "get /session_data is_success";
1124 1         424 $session = YAML::Load $res->content;
1125             ok !defined $session->{logged_in_user_lastlogin},
1126 1         1443 "... and logged_in_user_lastlogin is still not set in the session.";
1127              
1128             # check logged_in_user_lastlogin method
1129              
1130 1         372 $res = get('/logged_in_user_lastlogin');
1131 1         5 ok $res->is_success, "get /logged_in_user_lastlogin is_success";
1132 1 50       421 is $res->content, "not set",
1133             "... and logged_in_user_lastlogin returns undef"
1134             or diag explain $res->content;
1135              
1136             # logout
1137              
1138 1         436 $res = get('/logout');
1139 1         6 ok $res->is_redirect, "/logout is_success";
1140              
1141             # login again and now logged_in_user_lastlogin should be set
1142              
1143 1         414 $res =
1144             post( '/login', [ username => 'lastlogin1', password => 'lastlogin2' ] );
1145 1         6 ok $res->is_redirect, "POST /login with with new user is_redirect";
1146 1         500 is $res->header('location'), 'http://localhost/',
1147             "... and redirect location is correct.";
1148              
1149             # check we can reach restricted page
1150              
1151 1         362 $res = get('/loggedin');
1152 1         6 ok $res->is_success, "GET /loggedin is_success now we're logged in";
1153 1         432 is $res->content, "You are logged in", "... and we can see page content.";
1154              
1155             # check the session for logged_in_user_lastlogin
1156              
1157 1         354 $res = get('/session_data');
1158 1         6 ok $res->is_success, "get /session_data is_success";
1159 1         438 $session = YAML::Load $res->content;
1160             ok defined $session->{logged_in_user_lastlogin},
1161 1 50       1716 "... and logged_in_user_lastlogin is still not set in the session."
1162             or diag explain $session;
1163 1         355 like $session->{logged_in_user_lastlogin}, qr/^\d+$/,
1164             "... and session logged_in_user_lastlogin looks like an epoch time.";
1165              
1166             # check logged_in_user_lastlogin method
1167              
1168 1         333 $res = get('/logged_in_user_lastlogin');
1169 1         5 ok $res->is_success, "get /logged_in_user_lastlogin is_success";
1170 1         419 my $date = DateTime->now->ymd;
1171 1         246 is $res->content, $date,
1172             "... and logged_in_user_lastlogin is $date.";
1173              
1174             # cleanup
1175 1         348 get('/logout');
1176             }
1177              
1178             #------------------------------------------------------------------------------
1179             #
1180             # logged_in_user_password_expired
1181             #
1182             #------------------------------------------------------------------------------
1183              
1184             sub _logged_in_user_password_expired {
1185 1     1   892 my $res;
1186              
1187 1         5 my $data = [
1188             username => 'pwdexpired1',
1189             password => 'pwd1',
1190             realm => 'config1'
1191             ];
1192              
1193             on '2016-10-01 00:00:00' => sub {
1194 1     1   1466 $res = post( '/create_user',$data);
1195 1         4 ok $res->is_success, "create user pwdexpired1 is success on 2016-10-01";
1196 1         11316 is $res->content, 1,
1197             "... and it seems user was created based on response.";
1198              
1199 1         390 $res = get('/logged_in_user_password_expired');
1200 1         4 is $res->content, 'no',
1201             "... and before login logged_in_user_password_expired is false.";
1202              
1203 1         11590 post('/login', $data);
1204 1         5 $res = get('/loggedin');
1205 1         4 ok $res->is_success, "User is now logged in";
1206              
1207 1         524 $res = get('/logged_in_user_password_expired');
1208 1         5 is $res->content, 'no',
1209             "... and logged_in_user_password_expired is false.";
1210 1         9 };
1211              
1212 1         5545 note "... time passes ...";
1213              
1214             on '2016-11-01 00:00:00' => sub {
1215              
1216 1     1   1296 $res = get('/logged_in_user_password_expired');
1217 1         4 is $res->content, 'yes',
1218             "... and 30 days later logged_in_user_password_expired is true.";
1219              
1220 1         8693 post('/logout');
1221 1         570 };
1222             }
1223              
1224             #------------------------------------------------------------------------------
1225             #
1226             # password_reset
1227             #
1228             #------------------------------------------------------------------------------
1229              
1230             sub _password_reset {
1231 1     1   951 my ( $res, $code );
1232              
1233             # request password reset with non-existant user
1234              
1235 1         4 $Dancer2::Plugin::Auth::Extensible::Test::App::data = undef;
1236 1         20 $trap->read;
1237              
1238 1         83 $res = post( '/login',
1239             [ username_reset => 'NoSuchUser', submit_reset => 'truthy value' ] );
1240              
1241 1 50       7 ok $res->is_success, "POST /login with password reset request is_success"
1242             or diag explain $res;
1243              
1244 1 50       550 like $res->content, qr/A password reset request has been sent/,
1245             "... and we see \"A password reset request has been sent\" in page"
1246             or diag explain $trap->read;
1247              
1248 1 50       349 ok !defined $Dancer2::Plugin::Auth::Extensible::Test::App::data,
1249             "... and password_reset_send_email was not called."
1250             or diag explain $Dancer2::Plugin::Auth::Extensible::Test::App::data;
1251              
1252             # call /login/$code with bad code
1253              
1254 1         357 $res = get("/login/12345678901234567890123456789012");
1255              
1256 1 50       6 ok $res->is_success, "GET /login/<code> with bad code is_success"
1257             or diag explain $res;
1258              
1259 1         519 like $res->content, qr/You need to log in to continue/,
1260             "... and we have the /login page.";
1261              
1262             # request password reset with valid user
1263              
1264 1         345 $Dancer2::Plugin::Auth::Extensible::Test::App::data = undef;
1265 1         8 $trap->read;
1266              
1267 1         111 $res = post( '/login',
1268             [ username_reset => 'dave', submit_reset => 'truthy value' ] );
1269              
1270 1 50       8 ok $res->is_success, "POST /login with password reset request is_success"
1271             or diag explain $res;
1272              
1273 1 50       518 like $res->content, qr/A password reset request has been sent/,
1274             "... and we see \"A password reset request has been sent\" in page"
1275             or diag explain $trap->read;
1276              
1277 1         353 cmp_deeply $Dancer2::Plugin::Auth::Extensible::Test::App::data,
1278             {
1279             called => 1,
1280             code => re(qr/\w+/),
1281             email => ignore(),
1282             },
1283             "... and password_reset_send_email received code and email.";
1284              
1285 1         1527 $code = $Dancer2::Plugin::Auth::Extensible::Test::App::data->{code};
1286              
1287             # get /login/$code
1288              
1289 1         6 $trap->read;
1290 1         79 $res = get("/login/$code");
1291              
1292 1 50       8 ok $res->is_success, "GET /login/<code> with good code is_success"
1293             or diag explain $res;
1294              
1295 1         507 like $res->content,
1296             qr/Please click the button below to reset your password/,
1297             "... and we have the /login page with reset password link.";
1298              
1299             # post /login/$code with bad code
1300              
1301 1         347 $trap->read;
1302 1         76 $res = post(
1303             "/login/12345678901234567890123456789012",
1304             [ confirm_reset => "Reset password" ]
1305             );
1306 1 50       7 ok $res->is_success, "POST /login/<code> with bad code is_success",
1307             or diag explain $res;
1308 1         526 unlike $res->content, qr/Your new password is \w{8}\</,
1309             "... and we are NOT given a new password";
1310 1         345 like $res->content, qr/LOGIN FAILED/, "... but see LOGIN FAILED.";
1311              
1312             # post /login/$code with good code
1313              
1314 1         339 $trap->read;
1315 1         77 $res = post( "/login/$code", [ confirm_reset => "Reset password" ] );
1316 1 50       7 ok $res->is_success, "POST /login/<code> with good code is_success",
1317             or diag explain $res;
1318 1 50       719 like $res->content, qr/Your new password is \w{8}\</,
1319             "... and we are given a new password."
1320             or diag explain $trap->read;
1321              
1322             # reset dave's password for later tests
1323              
1324 1         328 $res =
1325             post( '/user_password', [ username => 'dave', new_password => 'beer' ] );
1326 1         5 is $res->content, "dave", "Reset dave's password to beer";
1327              
1328             }
1329              
1330             #------------------------------------------------------------------------------
1331             #
1332             # require_login
1333             #
1334             #------------------------------------------------------------------------------
1335              
1336             sub _require_login {
1337 2     2   1845 my ( $res, $logs );
1338              
1339             # check open / is ok
1340              
1341 2         9 $res = get('/');
1342 2         11 ok $res->is_success, "GET / is success - no login required";
1343              
1344             # we cannot reach require_login routes
1345              
1346 2         779 $res = get('/loggedin');
1347 2         11 ok $res->is_redirect, "GET /loggedin causes redirect";
1348 2         1093 is $res->header('location'),
1349             'http://localhost/login?return_url=%2Floggedin',
1350             "... and we're redirected to /login with return_url=/loggedin.";
1351              
1352             # regex route when not logged in
1353              
1354 2         718 $res = get('/regex/a');
1355 2         12 ok $res->is_redirect, "GET /regex/a causes redirect";
1356 2         965 is $res->header('location'),
1357             'http://localhost/login?return_url=%2Fregex%2Fa',
1358             "... and we're redirected to /login with return_url=/regex/a.";
1359              
1360             # login
1361              
1362 2         738 $res = post( '/login', [ username => 'dave', password => 'beer' ] );
1363 2         15 ok $res->is_redirect, "POST /login with good username/password is_redirect";
1364 2         956 is $res->header('location'), 'http://localhost/',
1365             "... and redirect location is correct.";
1366              
1367             # check we can reach restricted page
1368              
1369 2         743 $res = get('/loggedin');
1370 2         11 ok $res->is_success, "GET /loggedin is_success now we're logged in";
1371 2         887 is $res->content, "You are logged in", "... and we can see page content.";
1372              
1373             # regex route
1374              
1375 2         719 $res = get('/regex/a');
1376 2         9 ok $res->is_success, "GET /regex/a is_success now we're logged in";
1377 2         874 is $res->content, "Matched", "... and we can see page content.";
1378              
1379             # cleanup
1380 2         698 get('/logout');
1381              
1382             # require_login should receive a coderef
1383              
1384 2         13 $trap->read; # clear logs
1385 2         151 $res = get('/require_login_no_sub');
1386 2         11 $logs = $trap->read;
1387 2 50       123 is @$logs, 1, "One message in the logs" or diag explain $logs;
1388 2         883 is $logs->[0]->{level}, 'warning', "We got a warning in the logs";
1389             is $logs->[0]->{message},
1390 2         712 'Invalid require_login usage, please see docs',
1391             "Warning message is as expected";
1392 2         730 $trap->read; # clear logs
1393              
1394 2         119 $res = get('/require_login_not_coderef');
1395 2         10 $logs = $trap->read;
1396 2 50       128 is @$logs, 1, "One message in the logs" or diag explain $logs;
1397 2         791 is $logs->[0]->{level}, 'warning', "We got a warning in the logs";
1398             is $logs->[0]->{message},
1399 2         733 'Invalid require_login usage, please see docs',
1400             "Warning message is as expected";
1401             }
1402              
1403             #------------------------------------------------------------------------------
1404             #
1405             # roles
1406             #
1407             #------------------------------------------------------------------------------
1408              
1409             sub _roles {
1410              
1411             # make sure we're not logged in
1412              
1413             {
1414 2         11 $trap->read; # clear logs
1415              
1416 2         124 my $res = get('/loggedin');
1417              
1418 2 50       9 is( $res->code, 302, '[GET /loggedin] Correct code' )
1419             or diag explain $trap->read;
1420              
1421 2         826 is(
1422             $res->headers->header('Location'),
1423             'http://localhost/login?return_url=%2Floggedin',
1424             '/loggedin redirected to login page when not logged in'
1425             );
1426             }
1427              
1428             {
1429 2     2   1820 $trap->read;
  2         10  
1430 2         124 my $res = get('/user_roles');
1431 2         9 is $res->code, 500,
1432             "user_roles with no logged_in_user and no args dies";
1433 2         876 my $logs = $trap->read;
1434 2 50       131 cmp_deeply $logs,
1435             superbagof(
1436             {
1437             formatted => ignore(),
1438             level => 'error',
1439             message =>
1440             re(qr/user_roles needs a username or a logged in user/),
1441             }
1442             ),
1443             "got error: user_roles needs a username or a logged in user",
1444             or diag explain $logs;
1445             }
1446              
1447             # and can't reach pages that have require_role
1448              
1449             {
1450 2         713 $trap->read; # clear logs
  2         10  
1451              
1452 2         121 my $res = get('/beer');
1453              
1454 2 50       9 is( $res->code, 302, '[GET /beer] Correct code' )
1455             or diag explain $trap->read;
1456              
1457 2         777 is(
1458             $res->headers->header('Location'),
1459             'http://localhost/login?return_url=%2Fbeer',
1460             '/beer redirected to login page when not logged in'
1461             );
1462             }
1463              
1464             # ... and that we can log in with real details
1465              
1466             {
1467 2         3691 $trap->read; # clear logs
  2         13  
1468              
1469 2         126 my $res = post( '/login', [ username => 'dave', password => 'beer' ] );
1470              
1471 2 50       12 is( $res->code, 302, 'Login with real details succeeds' )
1472             or diag explain $trap->read;
1473              
1474 2         835 is get('/loggedin')->content, "You are logged in",
1475             "... and checking /loggedin route shows we are logged in";
1476             }
1477              
1478             # user_roles for logged_in_user
1479              
1480             {
1481 2         756 $trap->read; # clear logs
  2         11  
1482              
1483 2         138 my $res = get('/roles');
1484              
1485 2 50       8 is( $res->code, 200, 'get /roles is 200' )
1486             or diag explain $trap->read;
1487              
1488 2         817 is( $res->content, 'BeerDrinker,Motorcyclist',
1489             'Correct roles for logged in user' );
1490             }
1491              
1492             # user_roles for specific user
1493              
1494             {
1495 2         781 $trap->read; # clear logs
  2         9  
1496              
1497 2         126 my $res = get('/roles/bob');
1498              
1499 2 50       10 is( $res->code, 200, 'get /roles/bob is 200' )
1500             or diag explain $trap->read;
1501              
1502 2         855 is( $res->content, 'CiderDrinker',
1503             'Correct roles for other user in current realm' );
1504             }
1505              
1506             # Check we can request something which requires a role we have....
1507              
1508             {
1509 2         745 $trap->read; # clear logs
  2         39  
1510              
1511 2         131 my $res = get('/beer');
1512              
1513 2 50       9 is( $res->code, 200,
1514             'We can request a route (/beer) requiring a role we have...' )
1515             or diag explain $trap->read;
1516             }
1517              
1518             # Check we can request a route that requires any of a list of roles,
1519             # one of which we have:
1520              
1521             {
1522 2         739 $trap->read; # clear logs
  2         11  
1523              
1524 2         124 my $res = get('/anyrole');
1525              
1526 2 50       10 is( $res->code, 200,
1527             "We can request a multi-role route requiring with any one role" )
1528             or diag explain $trap->read;
1529             }
1530              
1531             {
1532 2         832 $trap->read; # clear logs
  2         9  
1533              
1534 2         123 my $res = get('/allroles');
1535              
1536 2 50       10 is( $res->code, 200,
1537             "We can request a multi-role route with all roles required" )
1538             or diag explain $trap->read;
1539             }
1540              
1541             {
1542 2         804 $trap->read; # clear logs
  2         10  
1543              
1544 2         125 my $res = get('/not_allroles');
1545              
1546 2 50       10 is( $res->code, 403, "/not_allroles response code 403" )
1547             or diag explain $trap->read;
1548 2         805 like $res->content, qr/Permission Denied/,
1549             "... and we got the Permission Denied page.";
1550             }
1551              
1552             {
1553 2         801 $trap->read; # clear logs
  2         11  
1554              
1555 2         126 my $res = get('/piss/regex');
1556              
1557 2 50       10 is( $res->code, 200,
1558             "We can request a route requiring a regex role we have" )
1559             or diag explain $trap->read;
1560             }
1561              
1562             # ... but can't request something requiring a role we don't have
1563              
1564             {
1565 2         750 $trap->read; # clear logs
  2         11  
1566              
1567 2         125 my $res = get('/piss');
1568              
1569 2 50       9 is( $res->code, 403,
1570             "route requiring a role we don't have gets response code 403" )
1571             or diag explain $trap->read;
1572 2         813 like $res->content, qr/Permission Denied/,
1573             "... and we got the Permission Denied page.";
1574             }
1575              
1576             # 2 arg user_has_role
1577              
1578             {
1579 2         871 $trap->read; # clear logs
  2         12  
1580              
1581 2         129 my $res = get('/does_dave_drink_beer');
1582 2 50       9 is $res->code, 200, "/does_dave_drink_beer response is 200"
1583             or diag explain $trap->read;
1584 2         804 ok $res->content, "yup - dave drinks beer";
1585             }
1586             {
1587 2         718 $trap->read; # clear logs
  2         10  
1588              
1589 2         128 my $res = get('/does_dave_drink_cider');
1590 2 50       8 is $res->code, 200, "/does_dave_drink_cider response is 200"
1591             or diag explain $trap->read;
1592 2         851 ok !$res->content, "no way does dave drink cider";
1593             }
1594             {
1595 2         684 $trap->read; # clear logs
  2         12  
1596              
1597 2         128 my $res = get('/does_undef_drink_beer');
1598 2 50       10 is $res->code, 200, "/does_undef_drink_beer response is 200"
1599             or diag explain $trap->read;
1600 2         818 ok !$res->content, "undefined users cannot drink";
1601             }
1602              
1603             # Now, log out
1604              
1605             {
1606 2         707 $trap->read; # clear logs
  2         11  
1607              
1608 2         135 my $res = get('/logout');
1609              
1610 2 50       10 is( $res->code, 302, 'Logging out returns 302' )
1611             or diag explain $trap->read;
1612              
1613 2         869 is( $res->headers->header('Location'),
1614             'http://localhost/',
1615             '/logout redirected to / (exit_page) after logging out' );
1616             }
1617              
1618             # Check we can't access protected pages now we logged out:
1619              
1620             {
1621 2         711 $trap->read; # clear logs
  2         11  
1622              
1623 2         125 my $res = get('/loggedin');
1624              
1625 2 50       10 is( $res->code, 302, 'Status code on accessing /loggedin after logout' )
1626             or diag explain $trap->read;
1627              
1628 2         860 is(
1629             $res->headers->header('Location'),
1630             'http://localhost/login?return_url=%2Floggedin',
1631             '/loggedin redirected to login page after logging out'
1632             );
1633             }
1634              
1635             {
1636 2         721 $trap->read; # clear logs
  2         12  
1637              
1638 2         121 my $res = get('/beer');
1639              
1640 2 50       10 is( $res->code, 302, 'Status code on accessing /beer after logout' )
1641             or diag explain $trap->read;
1642              
1643 2         829 is(
1644             $res->headers->header('Location'),
1645             'http://localhost/login?return_url=%2Fbeer',
1646             '/beer redirected to login page after logging out'
1647             );
1648             }
1649              
1650             # OK, log back in, this time as a user from the second realm
1651              
1652             {
1653 2         714 $trap->read; # clear logs
  2         11  
1654              
1655 2         128 my $res =
1656             post( '/login', { username => 'burt', password => 'bacharach' } );
1657              
1658 2 50       11 is( $res->code, 302, 'Login as user from second realm succeeds' )
1659             or diag explain $trap->read;
1660              
1661 2         893 my $logs = $trap->read;
1662 2         134 cmp_deeply $logs,
1663             superbagof(
1664             {
1665             formatted => ignore(),
1666             level => 'debug',
1667             message => 'config2 accepted user burt'
1668             }
1669             ),
1670             "... and we see expected message in logs.";
1671              
1672 2         5560 is get('/loggedin')->content, "You are logged in",
1673             "... and checking /loggedin route shows we are logged in";
1674             }
1675              
1676             # And that now we're logged in again, we can access protected pages
1677              
1678             {
1679 2         712 $trap->read; # clear logs
  2         12  
1680              
1681 2         129 my $res = get('/loggedin');
1682              
1683 2 50       9 is( $res->code, 200, 'Can access /loggedin now we are logged in again' )
1684             or diag explain $trap->read;
1685             }
1686              
1687             {
1688 2         870 $trap->read; # clear logs
  2         9  
1689              
1690 2         122 my $res = get('/roles/bob/config1');
1691              
1692 2 50       8 is( $res->code, 200, 'Status code on /roles/bob/config1 route.' )
1693             or diag explain $trap->read;
1694              
1695 2         836 is( $res->content, 'CiderDrinker',
1696             'Correct roles for other user in current realm' );
1697             }
1698              
1699             # Now, log out again
1700              
1701             {
1702 2         801 $trap->read; # clear logs
  2         713  
  2         11  
1703              
1704 2         128 my $res = post('/logout');
1705              
1706 2 50       9 is( $res->code, 302, 'Logging out returns 302' )
1707             or diag explain $trap->read;
1708              
1709 2         871 is( $res->headers->header('Location'),
1710             'http://localhost/',
1711             '/logout redirected to / (exit_page) after logging out' );
1712             }
1713              
1714             }
1715              
1716             #------------------------------------------------------------------------------
1717             #
1718             # update_current_user
1719             #
1720             #------------------------------------------------------------------------------
1721              
1722             sub _update_current_user {
1723              
1724             # no user logged in
1725              
1726 1     1   963 $trap->read;
1727 1         75 my $res = get("/update_current_user");
1728 1 50       6 ok $res->is_success, "get /update_current_user is_success"
1729             or diag explain $trap->read;
1730 1         504 cmp_deeply $trap->read,
1731             superbagof(
1732             {
1733             formatted => ignore(),
1734             level => 'debug',
1735             message =>
1736             'Could not update current user as no user currently logged in',
1737             }
1738             ),
1739             "Could not update current user as no user currently logged in";
1740              
1741 1         1852 for my $realm (qw/config1 config2/) {
1742              
1743             # Now we're going to update the current user
1744              
1745             {
1746 2         5 $trap->read; # clear logs
  2         12  
1747              
1748             # First login as the test user
1749 2         131 my $res = post(
1750             '/login',
1751             [
1752             username => 'mark',
1753             password => "wantscider",
1754             realm => $realm
1755             ]
1756             );
1757              
1758 2         13 is( $res->code, 302,
1759             "Login with real details succeeds (realm $realm)" );
1760              
1761 2         962 my $logs = $trap->read;
1762 2         141 cmp_deeply $logs,
1763             superbagof(
1764             {
1765             formatted => ignore(),
1766             level => 'debug',
1767             message => "$realm accepted user mark"
1768             }
1769             ),
1770             "... and we see expected message in logs.";
1771              
1772 2         5647 is get('/loggedin')->content, "You are logged in",
1773             "... and checking /loggedin route shows we are logged in";
1774              
1775 2         943 $trap->read; # clear logs
1776              
1777             # Update the "current" user, that we logged in above
1778 2         133 $res = get("/update_current_user");
1779 2 50       9 is $res->code, 200, "get /update_current_user is 200"
1780             or diag explain $trap->read;
1781              
1782 2         962 $trap->read; # clear logs
1783              
1784             # Check the update has worked
1785 2         137 $res = get("/get_user_mark/$realm");
1786 2 50       9 is $res->code, 200, "get /get_user_mark/$realm is 200"
1787             or diag explain $trap->read;
1788              
1789 2         1003 my $user = YAML::Load $res->content;
1790              
1791 2         206795 cmp_ok( $user->{name}, 'eq', "I love cider",
1792             "Name is now I love cider" );
1793              
1794 2         987 $trap->read; # clear logs
1795              
1796 2         150 $res = post('/logout');
1797             }
1798             }
1799             }
1800              
1801             #------------------------------------------------------------------------------
1802             #
1803             # update_user
1804             #
1805             #------------------------------------------------------------------------------
1806              
1807             sub _update_user {
1808              
1809             # update_user with no realm specified
1810              
1811 1     1   908 $trap->read;
1812 1         69 my $res = post("/update_user", [ username => "mark", name => "FooBar" ]);
1813 1         6 is $res->code, 500, "update_user with no realm specified croaks 500";
1814 1         479 my $logs = $trap->read;
1815 1 50       72 cmp_deeply $logs,
1816             superbagof(
1817             {
1818             formatted => ignore(),
1819             level => "error",
1820             message => re(
1821             qr/Realm must be specified when more than one realm configured/
1822             ),
1823             }
1824             ),
1825             "got log: Realm must be specified when more than one realm configured."
1826             or diag explain $logs;
1827              
1828 1         1898 for my $realm (qw/config1 config2/) {
1829              
1830             # First test a standard user details update.
1831              
1832             {
1833 2         11 $trap->read; # clear logs
1834              
1835             # Get the current user settings, and make sure name is not what
1836             # we're going to change it to.
1837 2         1474 my $res = get("/get_user_mark/$realm");
1838 2 50       12 is $res->code, 200, "get /get_user_mark/$realm is 200"
1839             or diag explain $trap->read;
1840              
1841 2         1189 my $user = YAML::Load $res->content;
1842 2   50     208713 my $name = $user->{name} || '';
1843 2         18 cmp_ok(
1844             $name, 'ne',
1845             "Wiltshire Apples $realm",
1846             "Name is not currently Wiltshire Apples $realm"
1847             );
1848             }
1849             {
1850 2         518 $trap->read; # clear logs
  2         14  
1851              
1852             # Update the user
1853 2         168 my $res = get("/update_user_name/$realm");
1854 2 50       11 is $res->code, 200, "get /update_user_name/$realm is 200"
1855             or diag explain $trap->read;
1856              
1857 2         1205 $trap->read; # clear logs
1858              
1859             # check it
1860 2         140 $res = get("/get_user_mark/$realm");
1861 2 50       18 is $res->code, 200, "get /get_user_mark/$realm is 200"
1862             or diag explain $trap->read;
1863              
1864 2         1282 my $user = YAML::Load $res->content;
1865             cmp_ok(
1866 2         216674 $user->{name}, 'eq',
1867             "Wiltshire Apples $realm",
1868             "Name is now Wiltshire Apples $realm"
1869             );
1870             }
1871              
1872             # log in user dave and whilst logged in change user mark
1873             {
1874 2         1433 my $res = post('/login', [username => 'dave', password => 'beer']);
  2         15280  
  2         11  
1875 2         14 ok $res->is_redirect, "login user dave";
1876              
1877 2         1467 $res = get('/logged_in_user');
1878 2         11 ok $res->is_success, "... and get logged_in_user is_success";
1879 2 50       1888 like $res->content, qr/David Precious/,
1880             "... and we see dave's details."
1881             or diag $res->content;
1882              
1883             # Update mark
1884 2         1102 $res = post(
1885             "/update_user",
1886             [
1887             realm => $realm,
1888             username => "mark",
1889             name => "No beer for me"
1890             ]
1891             );
1892 2 50       16 ok $res->is_success, "change mark's name is success"
1893             or diag explain $trap->read;
1894              
1895 2         2243 $trap->read; # clear logs
1896              
1897             # check it
1898 2         344 $res = get("/get_user_mark/$realm");
1899 2 50       66 ok $res->is_success, "get /get_user_mark/$realm is_success"
1900             or diag explain $trap->read;
1901              
1902 2         1755 my $user = YAML::Load $res->content;
1903 2         216951 is $user->{name}, "No beer for me",
1904             "... and mark's name is now No beer for me.";
1905              
1906 2         1315 $res = get('/logged_in_user');
1907 2         13 ok $res->is_success, "get logged_in_user is_success";
1908 2 50       1604 like $res->content, qr/David Precious/,
1909             "... and we see still see dave's details."
1910             or diag $res->content;
1911              
1912             }
1913             }
1914             }
1915              
1916             #------------------------------------------------------------------------------
1917             #
1918             # user_password
1919             #
1920             #------------------------------------------------------------------------------
1921              
1922             sub _user_password {
1923 1     1   1184 my $res;
1924              
1925             # user_password with valid username and password, no realm
1926              
1927 1         9 $trap->read;
1928 1         94 $res = post( '/user_password', [ username => 'dave', password => 'beer' ] );
1929              
1930 1 50       8 ok $res->is_success,
1931             "/user_password with valid username and password returns is_success"
1932             or diag explain $trap->read;
1933 1         864 is $res->content, 'dave', "... and it returned the username.";
1934              
1935             # user_password with valid username but bad password, no realm
1936              
1937 1         471 $trap->read;
1938 1         88 $res =
1939             post( '/user_password', [ username => 'dave', password => 'BadPW' ] );
1940              
1941 1 50       9 ok $res->is_success,
1942             "/user_password with valid username and password returns is_success"
1943             or diag explain $trap->read;
1944 1         866 ok !$res->content, "... and response is undef/empty.";
1945              
1946             # user_password with valid username and password and realm
1947              
1948 1         461 $trap->read;
1949 1         82 $res = post( '/user_password',
1950             [ username => 'dave', password => 'beer', realm => 'config1' ] );
1951              
1952 1 50       9 ok $res->is_success,
1953             "/user_password with valid username, password and realm is_success"
1954             or diag explain $trap->read;
1955 1         846 is $res->content, 'dave', "... and it returned the username.";
1956              
1957             # user_password with valid username and password but wrong realm
1958              
1959 1         453 $trap->read;
1960 1         76 $trap->read;
1961 1         51 $res = post( '/user_password',
1962             [ username => 'dave', password => 'beer', realm => 'config2' ] );
1963              
1964 1 50       8 ok $res->is_success,
1965             "/user_password with valid username, password but bad realm is_success"
1966             or diag explain $trap->read;
1967 1         743 ok !$res->content, "content shows fail";
1968              
1969             # now with logged_in_user
1970              
1971 1         421 $trap->read;
1972 1         89 $res = post( '/login', [ username => 'dave', password => 'beer' ] );
1973              
1974 1 50       8 is( $res->code, 302, 'Login with real details succeeds' )
1975             or diag explain $trap->read;
1976              
1977 1         764 is get('/loggedin')->content, "You are logged in",
1978             "... and checking /loggedin route shows we are logged in";
1979              
1980             # good password as only arg with logged in user
1981              
1982 1         795 $trap->read;
1983 1         88 $res = post( '/user_password', [ password => 'beer' ] );
1984 1 50       8 ok $res->is_success, "user_password password=beer is_success"
1985             or diag explain $trap->read;
1986 1         859 is $res->content, 'dave', "... and it returned the username.";
1987              
1988             # bad password as only arg with logged in user
1989              
1990 1         445 $trap->read;
1991 1         80 $res = post( '/user_password', [ password => 'cider' ] );
1992 1 50       8 ok $res->is_success, "user_password password=cider is_success"
1993             or diag explain $trap->read;
1994 1         806 ok !$res->content, "content shows fail";
1995              
1996             # logout
1997              
1998 1         424 $res = get('/logout');
1999 1         6 ok $res->is_redirect, "logout user dave is_redirect as expected";
2000 1         892 ok get('/loggedin')->is_redirect,
2001             "... and checking /loggedin route shows dave is logged out.";
2002              
2003             # search for user by code that no user has yet
2004              
2005 1         837 my $code = 'UserPasswordResetCode123';
2006 1         9 $trap->read;
2007 1         85 $res = post( '/user_password', [ code => $code ] );
2008 1 50       7 ok $res->is_success, "user_password with code no user has is_success"
2009             or diag explain $trap->read;
2010 1         832 ok !$res->content, "content shows fail";
2011              
2012             # add code to dave's account details
2013              
2014 1         426 $trap->read;
2015 1         80 $res = post( '/update_user',
2016             [ username => 'dave', realm => 'config1', pw_reset_code => $code ] );
2017 1         9 ok $res->is_success,
2018             "Add password reset code to dave's account details is_success.";
2019              
2020             # now search for dave using code
2021              
2022 1         886 $trap->read;
2023 1         81 $res = post( '/user_password', [ code => $code ] );
2024 1 50       9 ok $res->is_success, "user_password with code no user has is_success"
2025             or diag explain $trap->read;
2026 1         715 is $res->content, 'dave', "... and user dave was found.";
2027              
2028             # change password
2029              
2030 1         437 $trap->read;
2031 1         73 $res = post( '/user_password',
2032             [ username => 'dave', new_password => 'paleale' ] );
2033 1         7 ok $res->is_success,
2034             "Update password without giving old password is_success";
2035 1 50       658 is $res->content, 'dave', "... and it returns the username."
2036             or diag explain $trap->read;
2037            
2038             # try login with old password
2039              
2040 1         435 $trap->read;
2041 1         68 $res = post( '/login', [ username => 'dave', password => 'beer' ] );
2042              
2043 1 50       10 ok $res->is_success, 'Login with old password fails with 200 OK code'
2044             or diag explain $res;
2045              
2046 1         861 ok get('/loggedin')->is_redirect,
2047             "... and checking /loggedin route shows we are NOT logged in.";
2048              
2049             # now new password
2050              
2051 1         811 $trap->read;
2052 1         90 $res = post( '/login', [ username => 'dave', password => 'paleale' ] );
2053              
2054 1 50       7 is( $res->code, 302, 'Login with real details succeeds' )
2055             or diag explain $trap->read;
2056              
2057 1         783 is get('/loggedin')->content, "You are logged in",
2058             "... and checking /loggedin route shows we are logged in";
2059              
2060             # logout
2061              
2062 1         745 $res = get('/logout');
2063 1         7 ok $res->is_redirect, "logout user dave is_redirect as expected";
2064 1         835 ok get('/loggedin')->is_redirect,
2065             "... and checking /loggedin route shows dave is logged out.";
2066              
2067              
2068             # try to change password but supply bad old password
2069              
2070 1         776 $trap->read;
2071 1         93 $res = post( '/user_password',
2072             [ username => 'dave', password => 'bad', new_password => 'beer' ] );
2073 1         8 ok $res->is_success, "Update password with bad old password is_success";
2074 1 50       867 ok !$res->content, "... and it returns false."
2075             or diag explain $trap->read;
2076            
2077             # try to change password and supply good old password
2078              
2079 1         468 $trap->read;
2080 1         90 $res = post( '/user_password',
2081             [ username => 'dave', password => 'paleale', new_password => 'beer' ] );
2082 1         9 ok $res->is_success, "Update password with good old password is_success";
2083 1         821 is $res->content, 'dave', "... and user dave was found.";
2084            
2085             # try login with old password
2086              
2087 1         440 $trap->read;
2088 1         82 $res = post( '/login', [ username => 'dave', password => 'paleale' ] );
2089              
2090 1 50       8 ok $res->is_success, 'Login with old password fails with 200 OK code'
2091             or diag explain $res;
2092              
2093 1         849 ok get('/loggedin')->is_redirect,
2094             "... and checking /loggedin route shows we are NOT logged in.";
2095              
2096             # now new password
2097              
2098 1         906 $trap->read;
2099 1         99 $res = post( '/login', [ username => 'dave', password => 'beer' ] );
2100              
2101 1 50       6 is( $res->code, 302, 'Login with real details succeeds' )
2102             or diag explain $trap->read;
2103              
2104 1         813 is get('/loggedin')->content, "You are logged in",
2105             "... and checking /loggedin route shows we are logged in";
2106              
2107             # logout
2108              
2109 1         723 $res = get('/logout');
2110 1         7 ok $res->is_redirect, "logout user dave is_redirect as expected";
2111 1         819 ok get('/loggedin')->is_redirect,
2112             "... and checking /loggedin route shows dave is logged out.";
2113             }
2114              
2115             1;