App-GHGen

 view release on metacpan or  search on metacpan

t/function.t  view on Meta::CPAN

	is(
		$cost->{minutes_per_month},
		$cost->{runs_per_month} * $cost->{minutes_per_run},
		'minutes_per_month equals runs x minutes_per_run',
	);

	memory_cycle_ok($cost, 'workflow cost result has no circular references');
	diag(explain($cost)) if $ENV{TEST_VERBOSE};
};

subtest 'CostEstimator::estimate_savings - quantifies potential reductions' => sub {
	# Caching issue saves CI minutes.
	my @caching_issues = ({
		type    => 'performance',
		message => 'No dependency caching found - increases build times and costs',
	});
	my $savings = App::GHGen::CostEstimator::estimate_savings(\@caching_issues);
	ok($savings->{minutes} > 0,  'caching fix yields positive minute savings');
	ok($savings->{cost} >= 0,    'savings cost field is non-negative');
	ok(defined $savings->{details}, 'savings includes details array');

	# No issues -> no savings.
	my $zero = App::GHGen::CostEstimator::estimate_savings([]);
	is($zero->{minutes}, 0, 'no issues produces zero minute savings');

	memory_cycle_ok($savings, 'savings result has no circular references');
};

# ═══════════════════════════════════════════════════════════════════════
# 3.  App::GHGen::Detector
# ═══════════════════════════════════════════════════════════════════════

subtest 'Detector::get_project_indicators - returns structured indicator lists' => sub {
	my $perl_indicators = get_project_indicators('perl');
	ok(ref $perl_indicators eq 'ARRAY', 'perl indicators are an arrayref');
	ok(scalar @$perl_indicators > 0,   'perl indicator list is non-empty');
	ok((grep { /cpanfile/ } @$perl_indicators), 'cpanfile listed as perl indicator');

	# Requesting all indicators returns a hashref keyed by language.
	my $all = get_project_indicators();
	ok(ref $all eq 'HASH', 'no-arg call returns a hashref');
	ok(exists $all->{perl}, 'all-indicators hash includes perl');
	ok(exists $all->{node}, 'all-indicators hash includes node');

	# Unknown type returns undef.
	my $unknown = get_project_indicators('cobol');
	ok(!defined $unknown, 'unknown type returns undef');
};

subtest 'Detector::_detect_perl - scores current repo as a Perl project' => sub {
	# We are running inside App-GHGen which is a Perl project (has cpanfile,
	# Makefile.PL, lib/, t/).  The detector should give a positive score.
	my $score = App::GHGen::Detector::_detect_perl();
	ok($score > 0, "Perl repo scores positively ($score)");
	diag("_detect_perl score: $score") if $ENV{TEST_VERBOSE};
};

subtest 'Detector::_detect_node - scores zero in a Perl-only repo' => sub {
	my $score = App::GHGen::Detector::_detect_node();
	# The App-GHGen repo has no package.json, so the score must be zero (or low).
	# We cannot assert zero because the repo may contain incidental node files,
	# but we can assert it is lower than the Perl score.
	my $perl_score = App::GHGen::Detector::_detect_perl();
	ok($score < $perl_score, 'node score is lower than perl score in a Perl repo');
};

subtest 'Detector::detect_project_type - returns "perl" for this repository' => sub {
	# Running from the App-GHGen directory, which is definitively a Perl project.
	my $type = detect_project_type();
	is($type, 'perl', 'detect_project_type returns "perl" for App-GHGen repo');

	# In wantarray context it returns a ranked list of detections.
	my @ranked = detect_project_type();
	ok(scalar @ranked > 0, 'wantarray context returns non-empty list');
	is($ranked[0]->{type}, 'perl', 'highest-ranked type is still perl');
};

subtest 'Detector::detect_project_type - returns undef in an empty directory' => sub {
	in_tempdir(sub {
		my $type = detect_project_type();
		ok(!defined $type, 'empty directory returns undef');
	});
};

# ═══════════════════════════════════════════════════════════════════════
# 4.  App::GHGen::Fixer
# ═══════════════════════════════════════════════════════════════════════

subtest 'Fixer::can_auto_fix - accepts all four fixable types' => sub {
	for my $type (qw(performance security cost maintenance)) {
		ok(
			can_auto_fix({ type => $type }),
			"can_auto_fix returns true for '$type'",
		);
	}
	ok(
		!can_auto_fix({ type => 'unknown_type' }),
		'can_auto_fix returns false for unknown type',
	);
};

subtest 'Fixer::get_latest_version - maps known actions to current versions' => sub {
	is(
		App::GHGen::Fixer::get_latest_version('actions/checkout'),
		'v6',
		'actions/checkout maps to v6',
	);
	is(
		App::GHGen::Fixer::get_latest_version('actions/cache'),
		'v5',
		'actions/cache maps to v5',
	);
	# Unknown action falls back to a sensible default rather than dying.
	my $fallback = App::GHGen::Fixer::get_latest_version('unknown/action');
	ok(defined $fallback, 'unknown action returns a defined fallback version');
};

subtest 'Fixer::detect_and_create_cache_step - creates ecosystem-correct steps' => sub {
	my $npm_steps  = [{ run => 'npm ci' }];
	my $npm_step   = App::GHGen::Fixer::detect_and_create_cache_step($npm_steps);
	ok(defined $npm_step, 'npm step detected');



( run in 1.050 second using v1.01-cache-2.11-cpan-4ac696b4eb4 )