App-Project-Doctor

 view release on metacpan or  search on metacpan

t/integration.t  view on Meta::CPAN

#!/usr/bin/perl

use strict;
use warnings;

use Test::Most;
use Test::Returns;
use Test::Mockingbird qw(spy mock_scoped restore_all);
use Readonly;
use File::Temp qw(tempdir);
use File::Spec;
use File::Path qw(make_path);
use File::Basename qw(dirname);
use Scalar::Util qw(blessed);

# ── Constants ─────────────────────────────────────────────────────────────────

Readonly::Scalar my $VALID_VERSION   => '1.00';
Readonly::Scalar my $INVALID_VERSION => 'alpha';
Readonly::Scalar my $SOME_MOD        => 'Some::Undeclared';
Readonly::Scalar my $ANOTHER_MOD     => 'Some::Other';

Readonly::Hash my %MINIMAL_DISTRO => (
	'Makefile.PL'      => "use ExtUtils::MakeMaker;\n",
	'lib/My/Module.pm' => "package My::Module;\nuse strict;\nuse warnings;\nour \$VERSION = '1.00';\n1;\n",
	'Changes'          => "1.00 2026-06-30\n    - Initial release.\n",
	'MANIFEST'         => "MANIFEST\nMakefile.PL\nlib/My/Module.pm\n",
	'README'           => "My::Module -- a test module.\n",
);

# ── Module loading ─────────────────────────────────────────────────────────────

use_ok 'App::Project::Doctor';
use_ok 'App::Project::Doctor::Context';
use_ok 'App::Project::Doctor::Finding';
use_ok 'App::Project::Doctor::Report';
use_ok 'App::Project::Doctor::Fixer';

my $Doctor  = 'App::Project::Doctor';
my $Context = 'App::Project::Doctor::Context';
my $Finding = 'App::Project::Doctor::Finding';
my $Report  = 'App::Project::Doctor::Report';
my $Fixer   = 'App::Project::Doctor::Fixer';

# ── Shared helpers ────────────────────────────────────────────────────────────

# Build a temp directory tree from a %{ rel_path => content } hash.
sub _make_distro {
	my (%files) = @_;
	my $dir = tempdir(CLEANUP => 1);
	for my $rel (sort keys %files) {
		my @parts = split m{/}, $rel;
		my $abs   = File::Spec->catfile($dir, @parts);
		make_path(dirname($abs)) unless -d dirname($abs);
		open my $fh, '>', $abs or die "Cannot write $abs: $!";
		print $fh $files{$rel};
		close $fh;
	}
	return $dir;
}

# Create a Finding with the given named args.
sub _f {
	return $Finding->new(@_);
}

# Build a Report containing $n fixable error findings.
# Each fix coderef appends "fixN" to @{$applied_ref}.
sub _report_with_fixable {
	my ($applied_ref, $n) = @_;
	my $report = $Report->new;
	for my $i (1 .. $n) {
		my $idx = $i;
		$report->add_findings(_f(
			severity   => 'error',
			message    => "Fix me $idx",
			check_name => 'Test',
			fix        => sub { push @{$applied_ref}, "fix$idx" },
		));
	}
	return $report;
}

# Run Fixer interactively, supplying $input via a string-ref STDIN.
# Suppresses Fixer output unless $ENV{TEST_VERBOSE}.
sub _interactive_fixer {
	my ($input, $applied_ref) = @_;
	my $dir    = tempdir(CLEANUP => 1);
	my $ctx    = $Context->new(root => $dir);
	my $report = _report_with_fixable($applied_ref, 3);
	my $fixer  = $Fixer->new(report => $report, context => $ctx);

	my ($count, $out);
	{
		open(local *STDOUT, '>', \$out)  or die "Cannot redirect STDOUT: $!";
		open(local *STDIN,  '<', \$input) or die "Cannot redirect STDIN: $!";
		$count = $fixer->run;
	}
	diag "Fixer output:\n$out" if $ENV{TEST_VERBOSE} && $out;
	return $count;
}

# ─────────────────────────────────────────────────────────────────────────────
# 1. FINDING -> REPORT ACCUMULATION
# Strategy: Exercise the full Finding+Report pipeline by creating all four
# severity types and verifying that each filter method is independently correct.
# A passing report and a failing report are tested to ensure exit_code and the
# has_* predicates agree.
# ─────────────────────────────────────────────────────────────────────────────

subtest 'finding-report accumulation across all severity types' => sub {
	my $fix_called = 0;



( run in 0.869 second using v1.01-cache-2.11-cpan-af0e5977854 )