App-Project-Doctor
view release on metacpan or search on metacpan
# t/unit.t -- Black-box unit tests, one subtest per documented public API.
#
# Strategy: every test asserts a contract stated in the module's POD, not an
# implementation detail. Internal helpers (_collect_files, _worst_severity,
# etc.) are tested separately in t/function.t.
#
# Libraries:
# Test::Most -- strict mode + Test::More + Test::Exception exports
# Test::Mockingbird -- mock_scoped / spy / mock_return / mock_exception
# Test::Returns -- returns_ok / returns_not_ok (schema validation)
use strict;
use warnings;
use Test::Most;
use Test::Mockingbird;
use Test::Returns;
use File::Temp qw(tempdir);
use File::Spec;
use File::Path qw(make_path);
use Readonly;
use Scalar::Util qw(blessed);
# ---------------------------------------------------------------------------
# Test constants -- every magic string lives here, not in the subtests.
# ---------------------------------------------------------------------------
Readonly::Scalar my $SEV_ERROR => 'error';
Readonly::Scalar my $SEV_WARN => 'warning';
Readonly::Scalar my $SEV_PASS => 'pass';
Readonly::Scalar my $SEV_INFO => 'info';
Readonly::Hash my %ICON_FOR => (
$SEV_ERROR => '[X]',
$SEV_WARN => '[!]',
$SEV_PASS => '[v]',
$SEV_INFO => '[i]',
);
Readonly::Scalar my $DEFAULT_CHECK_NAME => 'Unknown';
Readonly::Scalar my $DEFAULT_CATEGORY => 'general';
Readonly::Scalar my $DEFAULT_ORDER => 50;
# ---------------------------------------------------------------------------
# Shared helpers
# ---------------------------------------------------------------------------
# Create a temporary distribution directory tree from a flat path=>content map.
sub make_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);
(my $parent = $abs) =~ s{[/\\][^/\\]+$}{};
make_path($parent) unless -d $parent;
open my $fh, '>', $abs or die "Cannot write $abs: $!";
print {$fh} $files{$rel};
close $fh;
}
return $dir;
}
# Build a minimal Finding, defaulting check_name so callers can omit it.
sub _f {
require App::Project::Doctor::Finding;
return App::Project::Doctor::Finding->new(check_name => 'Unit', @_);
}
# Build a minimal Context over an optional tempdir.
sub _ctx {
require App::Project::Doctor::Context;
return App::Project::Doctor::Context->new(root => shift // tempdir(CLEANUP => 1));
}
diag "Perl $]" if $ENV{TEST_VERBOSE};
# ===========================================================================
# 1. App::Project::Doctor::Finding
# ===========================================================================
require_ok 'App::Project::Doctor::Finding';
# --- Constructor and accessors ---------------------------------------------
subtest 'Finding::new -- documented defaults when only message supplied' => sub {
# POD: severity defaults to 'info', check_name to 'Unknown',
# detail to '', file to '', line undef.
my $f = App::Project::Doctor::Finding->new(message => 'minimal');
is $f->severity, $SEV_INFO, 'severity defaults to info';
is $f->check_name, $DEFAULT_CHECK_NAME, 'check_name defaults to Unknown';
is $f->detail, '', 'detail defaults to empty string';
is $f->file, '', 'file defaults to empty string';
ok !defined $f->line, 'line is undef by default';
};
subtest 'Finding::new -- all documented fields round-trip through accessors' => sub {
my $cref = sub { 1 };
my $f = App::Project::Doctor::Finding->new(
severity => $SEV_ERROR,
message => 'broken',
detail => 'extended',
fix => $cref,
check_name => 'Tests',
file => 'lib/A.pm',
line => 7,
);
is $f->severity, $SEV_ERROR, 'severity';
is $f->message, 'broken', 'message';
is $f->detail, 'extended', 'detail';
is $f->check_name, 'Tests', 'check_name';
is $f->file, 'lib/A.pm', 'file';
is $f->line, 7, 'line';
( run in 0.903 second using v1.01-cache-2.11-cpan-af0e5977854 )