App-Project-Doctor

 view release on metacpan or  search on metacpan

t/function.t  view on Meta::CPAN

	throws_ok { _make_ctx($dir)->find_files(undef) }
		qr/requires a directory/i, 'undef dir croaks';
};

subtest 'Context::_collect_files -- skips missing directories' => sub {
	# A dir name that does not exist on disk must be silently skipped, not
	# throw an error -- the user may not have all optional dirs present.
	my $dir = tempdir(CLEANUP => 1);
	my $ctx = _make_ctx($dir);
	lives_ok {
		$ctx->_collect_files(['nonexistent_dir'], sub { 1 });
	} 'non-existent dir in list is silently skipped';
	my $result = $ctx->_collect_files(['nonexistent_dir'], sub { 1 });
	is scalar @{$result}, 0, 'returns empty arrayref for missing dir';
};

subtest 'Context -- memory cycle check' => sub {
	my $dir = tempdir(CLEANUP => 1);
	memory_cycle_ok(_make_ctx($dir), 'Context has no circular references');
};

diag 'Context: done' if $ENV{TEST_VERBOSE};

# ===========================================================================
# 3. App::Project::Doctor::Check::Base
# ===========================================================================

require_ok 'App::Project::Doctor::Check::Base';

subtest 'Check::Base::new -- creates blessed object' => sub {
	my $b = App::Project::Doctor::Check::Base->new;
	isa_ok $b, 'App::Project::Doctor::Check::Base';
};

subtest 'Check::Base -- required methods croak with class name' => sub {
	my $b = App::Project::Doctor::Check::Base->new;

	# The error message must name the class so developers know which
	# subclass is missing the implementation.
	throws_ok { $b->name }
		qr/App::Project::Doctor::Check::Base must implement name/,
		'name() croaks with class name';

	throws_ok { $b->description }
		qr/App::Project::Doctor::Check::Base must implement description/,
		'description() croaks with class name';

	throws_ok { $b->check('ctx') }
		qr/App::Project::Doctor::Check::Base must implement check/,
		'check() croaks with class name';
};

subtest 'Check::Base -- optional method defaults' => sub {
	my $b = App::Project::Doctor::Check::Base->new;
	is $b->can_fix,  0,         'can_fix defaults to 0 (not just false)';
	is $b->category, 'general', 'category defaults to "general"';
	is $b->order,    50,        'order defaults to 50';
};

subtest 'Check::Base -- subclass inherits and overrides' => sub {
	# Create an inline subclass to verify inheritance mechanics.
	{
		package My::ConcreteCheck;
		use parent -norequire, 'App::Project::Doctor::Check::Base';
		sub name        { 'Concrete' }
		sub description { 'A test check.' }
		sub check       { () }
		sub can_fix     { 1 }
		sub order       { 99 }
	}

	my $c = My::ConcreteCheck->new;
	is $c->name,        'Concrete',       'overridden name';
	is $c->description, 'A test check.',  'overridden description';
	is $c->can_fix,     1,                'overridden can_fix';
	is $c->order,       99,               'overridden order';
	is $c->category,    'general',        'inherited category default';
};

subtest 'Check::Base -- subclass name appears in croak message' => sub {
	# If a subclass forgets to implement a method the error must point to
	# the subclass, not Base, so the developer knows where to look.
	{
		package My::IncompleteCheck;
		use parent -norequire, 'App::Project::Doctor::Check::Base';
		sub name        { 'Incomplete' }
		sub description { 'Missing check().' }
		# check() not implemented
	}
	my $ic = My::IncompleteCheck->new;
	throws_ok { $ic->check }
		qr/My::IncompleteCheck must implement check/,
		'subclass name appears in error';
};

diag 'Check::Base: done' if $ENV{TEST_VERBOSE};

# ===========================================================================
# 4. App::Project::Doctor::Report
# ===========================================================================

require_ok 'App::Project::Doctor::Report';

subtest 'Report::new -- starts empty and clean' => sub {
	my $r = App::Project::Doctor::Report->new;
	isa_ok $r, 'App::Project::Doctor::Report';
	is scalar($r->all_findings), 0, 'no findings on construction';
	is $r->exit_code,            0, 'exit_code 0 when clean';
	ok !$r->has_errors,             'has_errors false initially';
	ok !$r->has_warnings,           'has_warnings false initially';
};

subtest 'Report::add_findings -- appends and returns $self' => sub {
	my $r   = App::Project::Doctor::Report->new;
	my $f   = _make_finding(severity => $ERR_SEV, message => 'bad', check_name => 'X');
	my $ret = $r->add_findings($f);
	is $ret, $r, 'returns $self for chaining';
	is scalar($r->all_findings), 1, 'finding appended';
};

subtest 'Report::add_findings -- rejects non-Finding objects' => sub {

t/function.t  view on Meta::CPAN


	my $dir = make_distro(
		'LICENSE'   => "GNU GENERAL PUBLIC LICENSE\nVersion 2\n",
		'META.json' => '{}',
	);
	my @f = App::Project::Doctor::Check::License->new->check(_make_ctx($dir));
	my $lic_match_pass = grep { $_->severity eq $PASS_SEV } @f;
	ok $lic_match_pass, 'pass when LICENSE content matches declared META license';
};

subtest 'Check::License::_meta_license_id -- returns undef on parse failure' => sub {
	mock_exception 'CPAN::Meta::load_file' => 'parse error';
	is App::Project::Doctor::Check::License::_meta_license_id('/fake/META.yml'),
		undef, 'returns undef when CPAN::Meta cannot parse the file';
	restore_all 'CPAN::Meta';
};

diag 'Check::License: done' if $ENV{TEST_VERBOSE};

# ===========================================================================
# 11. Check::Meta
# ===========================================================================

require_ok 'App::Project::Doctor::Check::Meta';

subtest 'Check::Meta -- metadata' => sub {
	my $c = App::Project::Doctor::Check::Meta->new;
	is $c->name,    'META', 'name';
	is $c->can_fix, 0,      'can_fix false';
	is $c->order,   30,     'order';
};

subtest 'Check::Meta::check -- warning only when no META but builder exists' => sub {
	my $dir = make_distro('Makefile.PL' => '');
	my @f   = App::Project::Doctor::Check::Meta->new->check(_make_ctx($dir));
	my $meta_warn    = grep { $_->severity eq $WARN_SEV } @f;
	my $meta_err     = grep { $_->severity eq $ERR_SEV  } @f;
	ok  $meta_warn,  'warning when no META file';
	ok !$meta_err,   'no error when builder present';
};

subtest 'Check::Meta::check -- warning + error when no META and no builder' => sub {
	my $dir = tempdir(CLEANUP => 1);
	my @f   = App::Project::Doctor::Check::Meta->new->check(_make_ctx($dir));
	my $no_meta_warn = grep { $_->severity eq $WARN_SEV } @f;
	my $no_meta_err  = grep { $_->severity eq $ERR_SEV  } @f;
	ok $no_meta_warn, 'warning emitted';
	ok $no_meta_err,  'error emitted for missing builder';
};

subtest 'Check::Meta::check -- error when META parse fails' => sub {
	mock_exception 'CPAN::Meta::load_file' => 'bad yaml';
	my $dir = make_distro('META.yml' => 'invalid: [yaml');
	my @f   = App::Project::Doctor::Check::Meta->new->check(_make_ctx($dir));
	my $parse_err = grep { $_->severity eq $ERR_SEV && $_->message =~ /Failed to parse/i } @f;
	ok $parse_err, 'error emitted on parse failure';
	restore_all 'CPAN::Meta';
};

subtest 'Check::Meta::check -- error per missing required field' => sub {
	# license is intentionally absent to verify that field is required.
	my $struct = {
		name     => 'MyDist',
		version  => '0.01',
		author   => ['Test Author'],
		abstract => 'Something',
	};
	my $g = mock_scoped('CPAN::Meta',
		load_file => sub { return bless {}, 'CPAN::Meta' },
		as_struct  => sub { return $struct },
	);
	my $dir = make_distro('META.yml' => '');
	my @f   = App::Project::Doctor::Check::Meta->new->check(_make_ctx($dir));
	my $lic_field = grep { $_->message =~ /'license'/ } @f;
	ok $lic_field, 'error for missing license field';
};

subtest 'Check::Meta::check -- pass when all required fields present' => sub {
	my $struct = {
		name     => 'MyDist',
		version  => '0.01',
		author   => ['Author'],
		abstract => 'Desc',
		license  => 'gpl_2',
	};
	my $g = mock_scoped('CPAN::Meta',
		load_file => sub { return bless {}, 'CPAN::Meta' },
		as_struct  => sub { return $struct },
	);
	my $dir = make_distro('META.yml' => '');
	my @f   = App::Project::Doctor::Check::Meta->new->check(_make_ctx($dir));
	my $all_fields_pass = grep { $_->severity eq $PASS_SEV } @f;
	ok $all_fields_pass, 'pass when all fields present';
};

diag 'Check::Meta: done' if $ENV{TEST_VERBOSE};

# ===========================================================================
# 12. Check::Pod
# ===========================================================================

require_ok 'App::Project::Doctor::Check::Pod';

subtest 'Check::Pod -- metadata' => sub {
	my $c = App::Project::Doctor::Check::Pod->new;
	is $c->name,    'POD', 'name';
	is $c->can_fix, 1,     'can_fix true';
	is $c->order,   40,    'order';
};

subtest 'Check::Pod::check -- info when lib/ is empty' => sub {
	my $dir = make_distro('Makefile.PL' => '');
	my @f   = App::Project::Doctor::Check::Pod->new->check(_make_ctx($dir));
	my $no_pm_info = grep { $_->severity eq $INFO_SEV } @f;
	ok $no_pm_info, 'info when no .pm files';
};

subtest 'Check::Pod::check -- pass when module has valid POD' => sub {
	my $dir = make_distro(
		'lib/Good.pm' => "package Good;\n=head1 NAME\nGood - testing\n=cut\n1;\n",
	);



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