Fix a load of the documentation up. Fix overriding HTTP auth relam in the ->authentic...
[catagits/Catalyst-Authentication-Credential-HTTP.git] / t / basic.t
1 #!/usr/bin/perl
2 use strict;
3 use warnings;
4 use Test::More tests => 24;
5 use Test::MockObject::Extends;
6 use Test::MockObject;
7 use Test::Exception;
8 use HTTP::Headers;
9
10 my $m; BEGIN { use_ok($m = "Catalyst::Authentication::Credential::HTTP") }
11 can_ok( $m, "authenticate" );
12 can_ok( $m, "authorization_required_response" );
13
14 my $req = Test::MockObject->new;
15 my $req_headers = HTTP::Headers->new;
16 $req->set_always( headers => $req_headers );
17 my $res = Test::MockObject->new;
18 my $status;
19 $res->mock(status => sub { $status = $_[1] });
20 my $content_type;
21 $res->mock(content_type => sub { $content_type = $_[1] });
22 my $body;
23 my $headers;
24 $res->mock(body => sub { $body = $_[1] });
25 my $res_headers = HTTP::Headers->new;
26 $res->set_always( headers => $res_headers );
27 my $user = Test::MockObject->new;
28 $user->mock(get => sub { return shift->{$_[0]} });
29 my $find_user_opts;
30 my $realm = Test::MockObject->new;
31 $realm->mock( find_user => sub { $find_user_opts = $_[1]; return $user; });
32 $realm->mock( name => sub { 'foo' } );
33 my $c = Test::MockObject->new;
34 my $cache = Test::MockObject->new;
35 $cache->mock(set => sub { shift->{$_[0]} = $_[1] });
36 $cache->mock(get => sub { return shift->{$_[0]} });
37 $c->mock(cache => sub { $cache });
38 $c->mock(debug => sub { 0 });
39 my @login_info;
40 $c->mock( login => sub { shift; @login_info = @_; 1 } );
41 my $authenticated = 0;
42 $c->mock( set_authenticated => sub { $authenticated++; } );
43 $c->set_always( config => {} );
44 $c->set_always( req => $req );
45 $c->set_always( res => $res );
46 $c->set_always( request => $req );
47 $c->set_always( response => $res );
48
49 sub new_self {
50     my $config = { @_ };
51     my $raw_self = $m->new($config, $c, $realm);
52     return Test::MockObject::Extends->new( $raw_self );
53 }
54
55 # Normal auth, simple as possible.
56 # No credentials
57 my $self = new_self( type => 'any', password_type => 'clear', password_field => 'password' );
58 throws_ok {
59     $self->authenticate( $c, $realm );
60 } qr/^ $Catalyst::DETACH $/x, 'Calling authenticate for http auth without header detaches';
61 $user->{password} = 'bar';
62
63 # Wrong credentials
64 $req_headers->authorization_basic( qw/foo quux/ );
65 throws_ok {
66     $self->authenticate( $c, $realm );
67 } qr/^ $Catalyst::DETACH $/x, 'Calling authenticate for http auth without header detaches';
68
69 # Correct credentials
70 $req_headers->authorization_basic( qw/foo bar/ );
71 ok($self->authenticate($c, $realm), "auth successful with header");
72 is($authenticated, 1, 'authenticated once');
73 is_deeply( $find_user_opts, { username => 'foo'}, "login delegated");
74
75 # Test all the headers look good.
76 $req_headers->clear;
77 $res_headers->clear;
78 $c->clear;
79 throws_ok {
80     $self->authenticate( $c, $realm );
81 } qr/^ $Catalyst::DETACH $/x, "detached on no authorization required with bad auth";
82 is( $status, 401, "401 status code" );
83 is( $content_type, 'text/plain' );
84 is( $body, 'Authorization required.' );
85 like( ($res_headers->header('WWW-Authenticate'))[0], qr/^Digest/, "WWW-Authenticate header set: digest");
86 like( ($res_headers->header('WWW-Authenticate'))[0], qr/realm="foo"/, "WWW-Authenticate header set: digest realm");
87 like( ($res_headers->header('WWW-Authenticate'))[1], qr/^Basic/, "WWW-Authenticate header set: basic");
88 like( ($res_headers->header('WWW-Authenticate'))[1], qr/realm="foo"/, "WWW-Authenticate header set: basic realm");
89
90 $res_headers->clear;
91 # Check password_field works
92 {
93     my $self = new_self( type => 'any', password_type => 'clear', password_field => 'the_other_password' );
94     local $user->{password} = 'bar';
95     local $user->{the_other_password} = 'the_other_password';
96     $req_headers->authorization_basic( qw/foo the_other_password/ );
97     ok($self->authenticate($c, $realm), "auth successful with header and alternate password field");
98     $c->clear;
99     $req_headers->authorization_basic( qw/foo bar/ );
100     throws_ok {
101         $self->authenticate( $c, $realm );
102     } qr/^ $Catalyst::DETACH $/x, "detached on bad password (different password field)";
103 }
104
105 $req_headers->clear;
106 $res_headers->clear;
107 throws_ok {
108     $self->authenticate( $c, $realm, { realm => 'myrealm' }); # Override realm object's name method by doing this.
109 } qr/^ $Catalyst::DETACH $/x, "detached on no authorization supplied, overridden realm value";
110 is( $status, 401, "401 status code" );
111 is( $content_type, 'text/plain' );
112 is( $body, 'Authorization required.' );
113 like( ($res_headers->header('WWW-Authenticate'))[0], qr/realm="myrealm"/, "WWW-Authenticate header set: digest realm overridden");
114 like( ($res_headers->header('WWW-Authenticate'))[1], qr/realm="myrealm"/, "WWW-Authenticate header set: basic realm overridden");
115