Concierge-Auth
view release on metacpan or search on metacpan
t/06-io-failures.t view on Meta::CPAN
#!/usr/bin/env perl
use v5.36;
use strict;
use warnings;
use Test2::V0;
use File::Temp qw/tempdir/;
use lib 't/lib';
use MockBuiltins;
use Concierge::Auth;
use Concierge::Auth::Base;
use Concierge::Auth::Pwd;
use Concierge::Auth::Generators qw(gen_word_phrase);
# This file covers I/O failure branches that the rest of the suite can't
# reach without forcing flock/open/chmod/unlink to fail -- see
# t/lib/MockBuiltins.pm. Real filesystem tricks (deleting a file behind an
# object's back) are used instead of mocking wherever that's simpler.
#
# $MockBuiltins::FAIL_OPEN_PATH scopes the injected open() failure to one
# specific path, so it doesn't also break unrelated opens happening in the
# same call (e.g. Crypt::Passphrase::Argon2's own use of /dev/urandom,
# which would otherwise be permanently poisoned for the rest of the
# process the first time it's touched).
my $dir = tempdir( CLEANUP => 1 );
# Warm up Crypt::Passphrase::Argon2 (and its /dev/urandom access) before
# any mocking is active, so its own one-time module-load I/O is never at
# risk of being caught by a FAIL_OPEN toggle below.
Concierge::Auth::Pwd->new( no_file => 1 )->encryptPwd('warm-up-password');
# ========== Concierge::Auth::Base - required-method stubs ==========
subtest 'Base - unimplemented contract methods die' => sub {
for my $method (qw(new authenticate is_id_known enroll change_credentials revoke)) {
like(
dies { Concierge::Auth::Base->$method() },
qr/Subclass must implement $method/,
"$method stub dies with expected message",
);
}
};
# ========== new() - file open/chmod failures ==========
subtest 'new - open failure on existing file (read check)' => sub {
my $file = "$dir/new_read_fail.pwd";
open my $fh, '>', $file or die $!;
close $fh;
local $MockBuiltins::FAIL_OPEN = 1;
local $MockBuiltins::FAIL_OPEN_PATH = $file;
like(
dies { Concierge::Auth::Pwd->new( file => $file ) },
qr/Can't read auth file/,
'croaks when the existing file cannot be opened for reading',
);
};
subtest 'new - open failure on new file (create)' => sub {
my $file = "$dir/new_create_fail.pwd";
local $MockBuiltins::FAIL_OPEN = 1;
local $MockBuiltins::FAIL_OPEN_PATH = $file;
like(
dies { Concierge::Auth::Pwd->new( file => $file ) },
qr{Can't open/create auth file},
'croaks when a new file cannot be created',
);
};
subtest 'new - chmod failure is non-fatal' => sub {
my $file = "$dir/new_chmod_fail.pwd";
my $auth;
local $MockBuiltins::FAIL_CHMOD = 1;
my $w = warnings { $auth = Concierge::Auth::Pwd->new( file => $file ) };
ok( $auth, 'object is still constructed despite chmod failure' );
ok( scalar @$w, 'a warning was issued for the chmod failure' );
};
# ========== authenticate() ==========
subtest 'authenticate - open failure' => sub {
my $file = "$dir/auth_open.pwd";
my $auth = Concierge::Auth::Pwd->new( file => $file );
$auth->enroll('alice', 'password123');
local $MockBuiltins::FAIL_OPEN = 1;
local $MockBuiltins::FAIL_OPEN_PATH = $file;
my $result = $auth->authenticate('alice', 'password123');
ok( !$result->{success}, 'authenticate fails when file cannot be opened' );
like( $result->{message}, qr/Cannot open auth file/, 'message reflects open failure' );
};
t/06-io-failures.t view on Meta::CPAN
my $result = $nf->enroll('alice', 'password123');
ok( !$result->{success}, 'enroll fails when no file is set' );
};
subtest 'enroll - pfile set but file missing (skips duplicate check)' => sub {
my $file = "$dir/enroll_missing.pwd";
my $auth = Concierge::Auth::Pwd->new( file => $file );
unlink $file;
my $result = $auth->enroll('alice', 'password123');
ok( $result->{success}, 'enroll succeeds, recreating the file' );
ok( -e $file, 'file exists again after enroll' );
};
subtest 'enroll - open failure on duplicate check' => sub {
my $file = "$dir/enroll_open_check.pwd";
my $auth = Concierge::Auth::Pwd->new( file => $file );
$auth->enroll('alice', 'password123');
local $MockBuiltins::FAIL_OPEN = 1;
local $MockBuiltins::FAIL_OPEN_PATH = $file;
my $result = $auth->enroll('bob', 'password123');
ok( !$result->{success}, 'enroll fails when duplicate-check open fails' );
like( $result->{message}, qr/Cannot open auth file/, 'message reflects open failure' );
};
subtest 'enroll - flock failure on duplicate check' => sub {
my $file = "$dir/enroll_flock_check.pwd";
my $auth = Concierge::Auth::Pwd->new( file => $file );
$auth->enroll('alice', 'password123');
local $MockBuiltins::FAIL_FLOCK = 1;
my $result = $auth->enroll('bob', 'password123');
ok( !$result->{success}, 'enroll fails when duplicate-check lock fails' );
like( $result->{message}, qr/Cannot lock file for reading/, 'message reflects lock failure' );
};
subtest 'enroll - open failure on write (fresh file)' => sub {
my $file = "$dir/enroll_open_write.pwd";
my $auth = Concierge::Auth::Pwd->new( file => $file );
unlink $file; # skip the duplicate-check block entirely
local $MockBuiltins::FAIL_OPEN = 1;
local $MockBuiltins::FAIL_OPEN_PATH = $file;
my $result = $auth->enroll('alice', 'password123');
ok( !$result->{success}, 'enroll fails when the write-open fails' );
like( $result->{message}, qr/Cannot open auth file/, 'message reflects open failure' );
};
subtest 'enroll - flock failure on write' => sub {
my $file = "$dir/enroll_flock_write.pwd";
my $auth = Concierge::Auth::Pwd->new( file => $file );
unlink $file; # skip the duplicate-check block entirely
local $MockBuiltins::FAIL_FLOCK = 1;
my $result = $auth->enroll('alice', 'password123');
ok( !$result->{success}, 'enroll fails when the write-lock fails' );
like( $result->{message}, qr/Cannot lock file for writing/, 'message reflects lock failure' );
};
# ========== change_credentials() ==========
subtest 'change_credentials - pfile set but file missing' => sub {
my $file = "$dir/change_missing.pwd";
my $auth = Concierge::Auth::Pwd->new( file => $file );
$auth->enroll('alice', 'password123');
unlink $file;
my $result = $auth->change_credentials('alice', 'newpassword456');
ok( !$result->{success}, 'change_credentials fails when file has vanished' );
like( $result->{message}, qr/Auth file not OK/, 'message reflects file check failure' );
};
subtest 'change_credentials - open failure' => sub {
my $file = "$dir/change_open.pwd";
my $auth = Concierge::Auth::Pwd->new( file => $file );
$auth->enroll('alice', 'password123');
local $MockBuiltins::FAIL_OPEN = 1;
local $MockBuiltins::FAIL_OPEN_PATH = $file;
my $result = $auth->change_credentials('alice', 'newpassword456');
ok( !$result->{success}, 'change_credentials fails when file cannot be opened' );
like( $result->{message}, qr/Cannot open file/, 'message reflects open failure' );
};
subtest 'change_credentials - flock failure' => sub {
my $file = "$dir/change_flock.pwd";
my $auth = Concierge::Auth::Pwd->new( file => $file );
$auth->enroll('alice', 'password123');
local $MockBuiltins::FAIL_FLOCK = 1;
my $result = $auth->change_credentials('alice', 'newpassword456');
ok( !$result->{success}, 'change_credentials fails when file cannot be locked' );
like( $result->{message}, qr/Cannot lock file/, 'message reflects lock failure' );
};
# ========== revoke() ==========
subtest 'revoke - pfile set but file missing' => sub {
my $file = "$dir/revoke_missing.pwd";
my $auth = Concierge::Auth::Pwd->new( file => $file );
$auth->enroll('alice', 'password123');
unlink $file;
my $result = $auth->revoke('alice');
ok( !$result->{success}, 'revoke fails when file has vanished' );
like( $result->{message}, qr/no good/, 'message reflects file check failure' );
};
subtest 'revoke - open failure' => sub {
my $file = "$dir/revoke_open.pwd";
my $auth = Concierge::Auth::Pwd->new( file => $file );
$auth->enroll('alice', 'password123');
local $MockBuiltins::FAIL_OPEN = 1;
local $MockBuiltins::FAIL_OPEN_PATH = $file;
my $result = $auth->revoke('alice');
ok( !$result->{success}, 'revoke fails when file cannot be opened' );
like( $result->{message}, qr/Cannot open file/, 'message reflects open failure' );
};
subtest 'revoke - flock failure' => sub {
my $file = "$dir/revoke_flock.pwd";
my $auth = Concierge::Auth::Pwd->new( file => $file );
$auth->enroll('alice', 'password123');
local $MockBuiltins::FAIL_FLOCK = 1;
my $result = $auth->revoke('alice');
ok( !$result->{success}, 'revoke fails when file cannot be locked' );
like( $result->{message}, qr/Cannot lock file/, 'message reflects lock failure' );
};
subtest 'revoke - preserves unrelated records' => sub {
my $file = "$dir/revoke_preserve.pwd";
my $auth = Concierge::Auth::Pwd->new( file => $file );
$auth->enroll('alice', 'password123');
$auth->enroll('bob', 'password456');
my $result = $auth->revoke('alice');
ok( $result->{success}, 'revoke succeeds' );
my $bob_known = $auth->is_id_known('bob');
ok( $bob_known->{known}, 'unrelated record for bob is preserved' );
};
# ========== confirm/reject/reply - remaining branches ==========
subtest 'reply - scalar context with explicit message' => sub {
my $scalar = Concierge::Auth::Pwd::reply(1, 'custom message');
ok( $scalar, 'scalar context returns the bool value' );
};
# ========== setFile() ==========
( run in 0.390 second using v1.01-cache-2.11-cpan-aadc1410aed )