App-Project-Doctor
view release on metacpan or search on metacpan
# 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);
subtest 'Fixer::new -- stores all documented accessors' => sub {
# POD: accessors: report, context, non_interactive (default 0)
my $r = App::Project::Doctor::Report->new;
my $fx = App::Project::Doctor::Fixer->new(report => $r, context => $FIXER_CTX);
isa_ok $fx, 'App::Project::Doctor::Fixer';
is $fx->report, $r, 'report accessor';
is $fx->context, $FIXER_CTX, 'context accessor';
is $fx->non_interactive, 0, 'non_interactive defaults to 0';
};
subtest 'Fixer::new -- wrong type for report or context is rejected' => sub {
my $r = App::Project::Doctor::Report->new;
throws_ok {
App::Project::Doctor::Fixer->new(report => 'string', context => $FIXER_CTX)
} qr//i, 'non-Report object for report rejected';
throws_ok {
App::Project::Doctor::Fixer->new(report => $r, context => 'string')
} qr//i, 'non-Context object for context rejected';
};
subtest 'Fixer::run -- returns 0 immediately when no fixable findings' => sub {
# POD: "Returns the count of fixes applied."
my $r = App::Project::Doctor::Report->new;
$r->add_findings(_f(severity => $SEV_PASS, message => 'ok'));
my $fx = App::Project::Doctor::Fixer->new(
report => $r, context => $FIXER_CTX, non_interactive => 1
);
my $count = $fx->run;
( run in 2.628 seconds using v1.01-cache-2.11-cpan-600a1bdf6e4 )