Concierge-Auth

 view release on metacpan or  search on metacpan

t/03-auth.t  view on Meta::CPAN

use strict;
use warnings;
use Test2::V0;
use File::Temp qw/tempdir/;

use Concierge::Auth;
use Concierge::Auth::Pwd;

my $dir  = tempdir( CLEANUP => 1 );
my $file = "$dir/auth.pwd";
my $auth = Concierge::Auth->new( backend_class => 'Concierge::Auth::Pwd', file => $file );

# ========== enroll (was setPwd) ==========

subtest 'enroll - create user' => sub {
    my $result = $auth->enroll('alice', 'password123');
    ok( $result->{success}, 'enroll succeeds for new user' );
    is( $result->{user_id}, 'alice', 'result carries the user ID' );
    is( $result->{status}, 'created', 'status is created' );
};

subtest 'enroll - duplicate ID rejected' => sub {
    my $result = $auth->enroll('alice', 'otherpassword');
    ok( !$result->{success}, 'enroll rejects duplicate ID' );
    like( $result->{message}, qr/previously used/i, 'message mentions previously used' );
};

# ========== is_id_known (was checkID) ==========

subtest 'is_id_known - existing user' => sub {
    my $result = $auth->is_id_known('alice');
    ok( $result->{success}, 'is_id_known succeeds' );
    ok( $result->{known}, 'is_id_known finds existing user' );
};

subtest 'is_id_known - missing user' => sub {
    my $result = $auth->is_id_known('nonexistent');
    ok( $result->{success}, 'is_id_known still succeeds (no I/O error)' );
    ok( !$result->{known}, 'is_id_known reports missing user as not known' );
};

# ========== authenticate (was checkPwd) ==========

subtest 'authenticate - correct password' => sub {
    my $result = $auth->authenticate('alice', 'password123');
    ok( $result->{success}, 'authenticate succeeds with correct password' );
};

subtest 'authenticate - wrong password' => sub {
    my $result = $auth->authenticate('alice', 'wrongpassword');
    ok( !$result->{success}, 'authenticate rejects wrong password' );
    like( $result->{message}, qr/Invalid password/i, 'message mentions invalid password' );
};

subtest 'authenticate - missing user' => sub {
    my $result = $auth->authenticate('nonexistent', 'password123');
    ok( !$result->{success}, 'authenticate rejects missing user' );
    like( $result->{message}, qr/not found/i, 'message mentions not found' );
};

# ========== change_credentials (was resetPwd) ==========

subtest 'change_credentials - change password' => sub {
    my $result = $auth->change_credentials('alice', 'newpassword456');
    ok( $result->{success}, 'change_credentials succeeds' );
    is( $result->{user_id}, 'alice', 'result carries the user ID' );

    # Old password should fail
    my $old = $auth->authenticate('alice', 'password123');
    ok( !$old->{success}, 'old password fails after change' );

    # New password should succeed
    my $new = $auth->authenticate('alice', 'newpassword456');
    ok( $new->{success}, 'new password succeeds after change' );
};

subtest 'change_credentials - missing user' => sub {
    my $result = $auth->change_credentials('nonexistent', 'password123');
    ok( !$result->{success}, 'change_credentials rejects missing user' );
    like( $result->{message}, qr/not found/i, 'message mentions not found' );
};

# ========== revoke (was deleteID) ==========

subtest 'revoke - remove user' => sub {
    # First confirm user exists
    my $exists = $auth->is_id_known('alice');
    ok( $exists->{known}, 'user exists before revoke' );

    my $result = $auth->revoke('alice');
    ok( $result->{success}, 'revoke succeeds' );
    is( $result->{user_id}, 'alice', 'result carries the user ID' );

    # Confirm user is gone
    my $gone = $auth->is_id_known('alice');
    ok( !$gone->{known}, 'user is gone after revoke' );
};

subtest 'revoke - missing user' => sub {
    my $result = $auth->revoke('nonexistent');
    ok( !$result->{success}, 'revoke rejects missing user' );
    like( $result->{message}, qr/not found/i, 'message mentions not found' );
};

# ========== Validation failures passed through ==========

subtest 'validation failures - bad ID' => sub {
    my $result = $auth->enroll('', 'password123');
    ok( !$result->{success}, 'enroll rejects empty ID' );

    $result = $auth->is_id_known('x');
    ok( !$result->{known}, 'is_id_known reports too-short ID as not known' );

    $result = $auth->authenticate('', 'password123');
    ok( !$result->{success}, 'authenticate rejects empty ID' );

    $result = $auth->change_credentials('', 'password123');
    ok( !$result->{success}, 'change_credentials rejects empty ID' );

    $result = $auth->revoke('');
    ok( !$result->{success}, 'revoke rejects empty ID' );
};

subtest 'validation failures - bad password' => sub {
    my $result = $auth->enroll('bob', 'short');
    ok( !$result->{success}, 'enroll rejects short password' );

    $result = $auth->authenticate('bob', 'short');
    ok( !$result->{success}, 'authenticate rejects short password (no match found)' );

    $result = $auth->change_credentials('bob', 'short');
    ok( !$result->{success}, 'change_credentials rejects short password' );
};

subtest 'validation failures - undef/empty password arg' => sub {
    my $result = $auth->enroll('someuser', '');
    ok( !$result->{success}, 'enroll rejects empty string password' );
    like( $result->{message}, qr/empty/i, 'message mentions empty' );

    $result = $auth->change_credentials('someuser', '');
    ok( !$result->{success}, 'change_credentials rejects empty string password' );
    like( $result->{message}, qr/empty/i, 'message mentions empty' );
};

# ========== confirm/reject/reply response helpers ==========
# These backend-specific helpers remain on Concierge::Auth::Pwd (they
# back its file-management and generator wrapper methods, which retain
# the old dual-return convention). Tested here to cover their
# default-message branches.

subtest 'confirm - default message' => sub {
    my ($ok, $msg) = Concierge::Auth::Pwd::confirm();
    ok( $ok, 'confirm() with no arg returns true' );
    like( $msg, qr/confirmation/i, 'default confirmation message used' );
};

subtest 'reject - default message' => sub {
    my ($ok, $msg) = Concierge::Auth::Pwd::reject();
    ok( !$ok, 'reject() with no arg returns false' );
    like( $msg, qr/rejection/i, 'default rejection message used' );
};

subtest 'reply - single-arg forms' => sub {
    # With no message arg, the ternary picks the default based on $bool
    my ($r1, $m1) = Concierge::Auth::Pwd::reply(1);
    ok( $r1, 'reply(1) returns true' );
    like( $m1, qr/confirmation/i, 'truthy bool gives confirmation default' );

    my ($r0, $m0) = Concierge::Auth::Pwd::reply(0);
    ok( !$r0, 'reply(0) returns false' );
    like( $m0, qr/rejection/i, 'falsy bool gives rejection default' );

    # No args at all — $bool defaults to 0 via //
    my ($rn, $mn) = Concierge::Auth::Pwd::reply();
    ok( !$rn, 'reply() with no args returns false' );
};

# ========== encryptPwd ==========

subtest 'encryptPwd - valid password' => sub {
    my $hash = $auth->encryptPwd('password123');
    ok( $hash, 'encryptPwd returns a truthy value' );
    like( $hash, qr/^\$argon2/, 'hash has Argon2 format' );
};

subtest 'encryptPwd - invalid password rejected' => sub {
    my $result = $auth->encryptPwd('short');
    ok( !$result, 'encryptPwd returns falsy for invalid password' );

    my ($ok, $msg) = $auth->encryptPwd('short');
    ok( !$ok, 'list context: encryptPwd returns false for invalid password' );
    like( $msg, qr/between/i, 'message mentions length requirement' );
};

# ========== no-file error paths ==========

subtest 'is_id_known - no file configured' => sub {
    my $nf;
    my $w = warnings { $nf = Concierge::Auth->new( backend_class => 'Concierge::Auth::Pwd', no_file => 1 ) };
    like( $w->[0], qr/Utilities only/i, 'constructor warns when no_file' );
    my $result = $nf->is_id_known('alice');
    ok( $result->{success}, 'is_id_known still succeeds when no file is set' );
    ok( !$result->{known}, 'ID reported not known when no file is set' );
};

subtest 'authenticate - no file configured' => sub {
    my $nf;
    my $w = warnings { $nf = Concierge::Auth->new( backend_class => 'Concierge::Auth::Pwd', no_file => 1 ) };
    like( $w->[0], qr/Utilities only/i, 'constructor warns when no_file' );
    my $result;
    my $pw = warnings { $result = $nf->authenticate('alice', 'password123') };
    ok( !$result->{success}, 'authenticate fails when no file is set' );
};

subtest 'revoke - no file configured' => sub {
    my $nf;
    my $w = warnings { $nf = Concierge::Auth->new( backend_class => 'Concierge::Auth::Pwd', no_file => 1 ) };
    like( $w->[0], qr/Utilities only/i, 'constructor warns when no_file' );
    my $result = $nf->revoke('alice');
    ok( !$result->{success}, 'revoke fails when no file is set' );
};

subtest 'change_credentials - no file configured' => sub {
    my $nf;
    my $w = warnings { $nf = Concierge::Auth->new( backend_class => 'Concierge::Auth::Pwd', no_file => 1 ) };
    like( $w->[0], qr/Utilities only/i, 'constructor warns when no_file' );
    my $result = $nf->change_credentials('alice', 'password123');
    ok( !$result->{success}, 'change_credentials fails when no file is set' );
    like( $result->{message}, qr/Not OK/i, 'message reflects file validation failure' );
};

done_testing;



( run in 0.805 second using v1.01-cache-2.11-cpan-aadc1410aed )