App-Project-Doctor

 view release on metacpan or  search on metacpan

t/unit.t  view on Meta::CPAN

	# 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 )