File Coverage

blib/lib/Dancer2/Plugin/Auth/Extensible/Test.pm
Criterion Covered Total %
statement 674 675 99.8
branch 110 212 51.8
condition 4 7 57.1
subroutine 28 29 96.5
pod 2 4 50.0
total 818 927 88.2


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