App-Project-Doctor
view release on metacpan or search on metacpan
is scalar($r->warnings), 1, 'warnings() count';
is scalar($r->passes), 1, 'passes() count';
is scalar($r->fixable), 1, 'fixable() count (only error-with-fix)';
is scalar($r->all_findings), 6, 'all_findings() count';
};
subtest 'Report::has_errors / has_warnings -- return exactly 1 or 0' => sub {
my $r_clean = App::Project::Doctor::Report->new;
$r_clean->add_findings(_f(severity => $SEV_PASS, message => 'ok'));
is $r_clean->has_errors, 0, 'has_errors exactly 0 when no errors';
is $r_clean->has_warnings, 0, 'has_warnings exactly 0 when no warnings';
my $r_bad = App::Project::Doctor::Report->new;
$r_bad->add_findings(
_f(severity => $SEV_ERROR, message => 'e'),
_f(severity => $SEV_WARN, message => 'w'),
);
is $r_bad->has_errors, 1, 'has_errors exactly 1';
is $r_bad->has_warnings, 1, 'has_warnings exactly 1';
};
subtest 'Report::exit_code -- 0 for warnings-only, 1 for errors' => sub {
# POD: "Returns 0 (clean) or 1 (errors present)."
my $r_warn = App::Project::Doctor::Report->new;
$r_warn->add_findings(_f(severity => $SEV_WARN, message => 'w'));
is $r_warn->exit_code, 0, 'warnings alone give exit_code 0';
my $r_err = App::Project::Doctor::Report->new;
$r_err->add_findings(_f(severity => $SEV_ERROR, message => 'e'));
is $r_err->exit_code, 1, 'errors give exit_code 1';
returns_ok($r_err->exit_code, { type => 'integer' }, 'exit_code returns integer');
};
subtest 'Report::render_text -- uses worst-severity icon per check group' => sub {
# POD: groups by check_name; icon reflects the worst finding in the group.
my $r = App::Project::Doctor::Report->new;
$r->add_findings(
_f(severity => $SEV_PASS, message => 'clean', check_name => 'Tests'),
_f(severity => $SEV_ERROR, message => 'broke', check_name => 'Tests'),
);
my $text = $r->render_text;
returns_ok($text, { type => 'scalar' }, 'render_text returns scalar');
like $text, qr/\[X\].*Tests/, 'error icon shown (worst wins over pass)';
like $text, qr/1 error\(s\)/, 'error count in summary';
};
subtest 'Report::render_text -- verbose exposes detail; non-verbose hides it' => sub {
my $r = App::Project::Doctor::Report->new;
$r->add_findings(_f(
severity => $SEV_ERROR,
message => 'Problem',
detail => 'Extended explanation',
check_name => 'Pod',
));
unlike $r->render_text(verbose => 0), qr/Extended explanation/,
'detail hidden in non-verbose mode';
like $r->render_text(verbose => 1), qr/Extended explanation/,
'detail shown in verbose mode';
};
subtest 'Report::render_text -- fix prompt only when fixable findings exist' => sub {
my $r_fix = App::Project::Doctor::Report->new;
$r_fix->add_findings(_f(
severity => $SEV_ERROR,
message => 'Fixme',
check_name => 'CI',
fix => sub {},
));
like $r_fix->render_text, qr/Suggested fixes:/, 'fix list header present';
like $r_fix->render_text, qr/Would you like me to apply/, 'prompt present';
my $r_nof = App::Project::Doctor::Report->new;
$r_nof->add_findings(_f(severity => $SEV_PASS, message => 'ok', check_name => 'CI'));
unlike $r_nof->render_text, qr/Suggested fixes:/, 'no fix list when unfixable';
};
subtest 'Report::render_text -- "No errors or warnings" when fully clean' => sub {
my $r = App::Project::Doctor::Report->new;
$r->add_findings(_f(severity => $SEV_PASS, message => 'ok', check_name => 'A'));
like $r->render_text, qr/No errors or warnings/, 'clean summary text';
};
subtest 'Report::render_json -- parseable UTF-8 JSON; fix excluded' => sub {
# POD: "Returns findings as a pretty-printed JSON string."
my $r = App::Project::Doctor::Report->new;
$r->add_findings(_f(
severity => $SEV_INFO,
message => 'noted',
check_name => 'Meta',
fix => sub {},
));
my $json = $r->render_json;
returns_ok($json, { type => 'scalar' }, 'render_json returns scalar');
require JSON::MaybeXS;
my $decoded = JSON::MaybeXS->new->decode($json);
is ref $decoded, 'ARRAY', 'decodes to arrayref';
ok !exists $decoded->[0]{fix}, 'fix key absent from JSON';
is $decoded->[0]{severity}, 'info', 'severity in JSON';
is $decoded->[0]{check_name}, 'Meta', 'check_name in JSON';
};
subtest 'Report::render_tap -- plan line; pass/info = ok, error/warning = not ok' => sub {
# POD: "Returns a TAP-format string for CI pipeline consumption."
my $r = App::Project::Doctor::Report->new;
$r->add_findings(
_f(severity => $SEV_PASS, message => 'p', check_name => 'A'),
_f(severity => $SEV_INFO, message => 'i', check_name => 'B'),
_f(severity => $SEV_WARN, message => 'w', check_name => 'C'),
_f(severity => $SEV_ERROR, message => 'e', check_name => 'D'),
);
my $tap = $r->render_tap;
returns_ok($tap, { type => 'scalar' }, 'render_tap returns scalar');
like $tap, qr/^1\.\.4/m, 'plan line present and correct';
like $tap, qr/^ok 1/m, 'pass = ok';
like $tap, qr/^ok 2/m, 'info = ok';
like $tap, qr/^not ok 3/m, 'warning = not ok';
like $tap, qr/^not ok 4/m, 'error = not ok';
};
diag 'Report: OK' if $ENV{TEST_VERBOSE};
# ===========================================================================
# 5. App::Project::Doctor::Fixer
# ===========================================================================
require_ok 'App::Project::Doctor::Fixer';
# Build a real Context once -- Fixer::new validates the type.
Readonly::Scalar my $FIXER_DIR => make_distro('Makefile.PL' => '');
my $FIXER_CTX = _ctx($FIXER_DIR);
( run in 0.634 second using v1.01-cache-2.11-cpan-6aa56a78535 )