App-Project-Doctor
view release on metacpan or search on metacpan
t/edge_cases.t view on Meta::CPAN
#
# Hostile, pathological, boundary-condition, and security tests.
# Strategy: actively try to break or subvert each module.
#
use strict;
use warnings;
use Test::Most;
use Test::Returns;
use Test::Mockingbird qw(mock_scoped restore_all);
use Readonly;
use File::Temp qw(tempdir);
use File::Spec;
use File::Path qw(make_path);
use File::Basename qw(dirname);
use Scalar::Util qw(blessed);
our $INJECT_SENTINEL;
# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------
Readonly::Scalar my $EMPTY => '';
Readonly::Scalar my $WHITESPACE => " \t\n ";
Readonly::Scalar my $HUGE => 'A' x 1_000_000;
Readonly::Scalar my $VALID_MSG => 'A valid finding message';
Readonly::Scalar my $Finding => 'App::Project::Doctor::Finding';
Readonly::Scalar my $Context => 'App::Project::Doctor::Context';
Readonly::Scalar my $Report => 'App::Project::Doctor::Report';
Readonly::Scalar my $Fixer => 'App::Project::Doctor::Fixer';
Readonly::Scalar my $Doctor => 'App::Project::Doctor';
Readonly::Scalar my $SEV_ERROR => 'error';
Readonly::Scalar my $SEV_WARNING => 'warning';
Readonly::Scalar my $SEV_PASS => 'pass';
Readonly::Scalar my $SEV_INFO => 'info';
# ---------------------------------------------------------------------------
# Module bootstrap
# ---------------------------------------------------------------------------
use_ok $Finding;
use_ok $Context;
use_ok $Report;
use_ok $Fixer;
use_ok $Doctor;
require App::Project::Doctor::Check::Security;
require App::Project::Doctor::Check::Dependencies;
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
# Build a minimal temp distro from a { relative/path => content } hash.
sub _distro {
my (%files) = @_;
my $dir = tempdir(CLEANUP => 1);
for my $rel (sort keys %files) {
my @parts = split m{/}, $rel;
my $abs = File::Spec->catfile($dir, @parts);
make_path(dirname($abs)) unless -d dirname($abs);
open my $fh, '>', $abs or die "Cannot write $abs: $!";
print $fh $files{$rel};
close $fh;
}
return $dir;
}
# Return a Context rooted at a fresh temp dir (or supplied dir).
sub _ctx {
my $dir = shift // tempdir(CLEANUP => 1);
return $Context->new(root => $dir);
}
# Minimal valid Finding with optional overrides.
sub _f {
my (%args) = @_;
return $Finding->new(
message => $VALID_MSG,
check_name => 'EdgeTest',
severity => $SEV_ERROR,
%args,
);
}
# Empty Report with N added findings.
sub _report_with {
my (@f) = @_;
my $r = $Report->new;
$r->add_findings(@f) if @f;
return $r;
}
# Run the Fixer in interactive mode, feeding $input as STDIN.
# Captures STDOUT into $$out_ref and $SIG{__WARN__} into @$warn_ref.
sub _run_fixer_interactive {
my ($report, $input, $out_ref, $warn_ref) = @_;
my $ctx = _ctx();
my $fixer = $Fixer->new(report => $report, context => $ctx);
my $count;
{
open(local *STDOUT, '>', $out_ref) or die;
open(local *STDIN, '<', \$input) or die;
local $SIG{__WARN__} = sub { push @{$warn_ref}, $_[0] };
$count = $fixer->run;
}
return $count;
}
# ===========================================================================
# Finding -- hostile constructor inputs
# ===========================================================================
subtest 'Finding::new -- undef message croaks immediately (pre-PVS guard)' => sub {
# The early guard fires before Params::Validate::Strict sees the args;
# the documented error message must be exact.
throws_ok { $Finding->new(message => undef, check_name => 'T') }
qr/message must be a non-empty string/,
'undef message produces documented croak';
( run in 0.566 second using v1.01-cache-2.11-cpan-af0e5977854 )