App-Project-Doctor

 view release on metacpan or  search on metacpan

t/integration.t  view on Meta::CPAN


	my $text = $report->render_text;
	like( $text, qr/\[v\]/,    'pass icon [v] present' );
	like( $text, qr/\[X\]/,    'error icon [X] present' );
	like( $text, qr/\[i\]/,    'info icon [i] present' );
	like( $text, qr/Tests/,    'check_name Tests appears in output' );
	like( $text, qr/Security/, 'check_name Security appears in output' );
	like( $text, qr/1 error/,  'error count in summary line' );
	unlike( $text, qr/No errors or warnings/, 'no-error message absent when errors present' );

	diag $text if $ENV{TEST_VERBOSE};
};

subtest 'render_text verbose mode shows detail; non-verbose hides it' => sub {
	my $detail_text = 'Extended diagnostic detail here';
	my $report = $Report->new;
	$report->add_findings(_f(
		severity   => 'error',
		message    => 'Primary message',
		detail     => $detail_text,
		check_name => 'TestCheck',
	));

	my $plain   = $report->render_text(verbose => 0);
	my $verbose = $report->render_text(verbose => 1);

	unlike( $plain,   qr/\Q$detail_text\E/, 'detail hidden when verbose => 0' );
	like(   $verbose, qr/\Q$detail_text\E/, 'detail shown when verbose => 1' );
};

subtest 'render_tap produces a valid TAP stream' => sub {
	my $report = $Report->new;
	$report->add_findings(
		_f(severity => 'pass',  message => 'Tests ok',  check_name => 'Tests'),
		_f(severity => 'error', message => 'POD error', check_name => 'POD'),
	);

	my $tap = $report->render_tap;
	like( $tap, qr/^1\.\.2$/m,    'TAP plan line 1..2' );
	like( $tap, qr/^ok 1\b/m,     'first finding is ok' );
	like( $tap, qr/^not ok 2\b/m, 'second (error) finding is not ok' );
	like( $tap, qr/\[Tests\]/,    'check_name included in TAP line' );

	diag $tap if $ENV{TEST_VERBOSE};
};

subtest 'render_json returns parseable array with expected keys; fix excluded' => sub {
	my $report = $Report->new;
	$report->add_findings(_f(
		severity   => 'error',
		message    => 'A JSON finding',
		detail     => 'some detail',
		check_name => 'TestCheck',
		file       => 'lib/T.pm',
		line       => 42,
		fix        => sub { 1 },
	));

	require JSON::MaybeXS;
	my $json_str = $report->render_json;
	my $decoded  = JSON::MaybeXS->new->decode($json_str);

	ok( ref $decoded eq 'ARRAY',            'decoded JSON is an array' );
	is( scalar @{$decoded}, 1,              'one element in array' );
	is( $decoded->[0]{severity},   'error', 'severity key correct' );
	is( $decoded->[0]{message}, 'A JSON finding', 'message key correct' );
	is( $decoded->[0]{check_name}, 'TestCheck',   'check_name key correct' );
	is( $decoded->[0]{file},       'lib/T.pm',    'file key correct' );
	is( $decoded->[0]{line},       42,             'line key correct' );
	ok( !exists $decoded->[0]{fix},                'fix coderef excluded from JSON output' );

	diag $json_str if $ENV{TEST_VERBOSE};
};

# ─────────────────────────────────────────────────────────────────────────────
# 3. OPTIONAL DEPENDENCY: JSON::MaybeXS
# Strategy: mock_scoped replaces JSON::MaybeXS::new with a sub that throws,
# simulating the module being unavailable. This is more portable than
# Test::Without::Module + INC manipulation, which behaves differently across
# Perl versions (runtime eval of 'use' does not reliably install the @INC hook).
# ─────────────────────────────────────────────────────────────────────────────

subtest 'render_json throws when JSON::MaybeXS backend fails' => sub {
	require JSON::MaybeXS;

	my $report = $Report->new;
	$report->add_findings(_f(severity => 'info', message => 'x', check_name => 'T'));

	# Replace new() for the duration of this block; the guard restores it on exit.
	my $g = mock_scoped 'JSON::MaybeXS::new'
		=> sub { die "Simulated: JSON::MaybeXS unavailable\n" };

	throws_ok { $report->render_json }
		qr/JSON::MaybeXS unavailable/,
		'render_json propagates exception when JSON::MaybeXS backend fails';
};

# ─────────────────────────────────────────────────────────────────────────────
# 4. FIXER NON-INTERACTIVE
# Strategy: Verify that the Report->Fixer->apply_all workflow fires each fix
# coderef exactly once (in insertion order) and returns the correct count.
# A non-fixable finding is included to confirm it is not counted.
# ─────────────────────────────────────────────────────────────────────────────

subtest 'fixer non_interactive applies all fixable findings and returns correct count' => sub {
	my $dir    = tempdir(CLEANUP => 1);
	my $ctx    = $Context->new(root => $dir);
	my @applied;
	my $report = _report_with_fixable(\@applied, 3);

	# Add a non-fixable finding to confirm it does not inflate the count.
	$report->add_findings(_f(severity => 'info', message => 'No fix', check_name => 'T'));

	my $fixer = $Fixer->new(report => $report, context => $ctx, non_interactive => 1);
	my ($count, $out);
	{
		open(local *STDOUT, '>', \$out) or die;
		$count = $fixer->run;
	}

	is( $count, 3,                          'three fixable findings applied' );
	is_deeply( \@applied, [qw(fix1 fix2 fix3)], 'fix coderefs called in insertion order' );

	diag "Fixer output:\n$out" if $ENV{TEST_VERBOSE};
};

subtest 'fixer returns 0 immediately when report has no fixable findings' => sub {
	my $dir    = tempdir(CLEANUP => 1);
	my $ctx    = $Context->new(root => $dir);
	my $report = $Report->new;



( run in 1.613 second using v1.01-cache-2.11-cpan-600a1bdf6e4 )