App-Project-Doctor

 view release on metacpan or  search on metacpan

t/unit.t  view on Meta::CPAN

	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 )