App-GHGen

 view release on metacpan or  search on metacpan

t/function.t  view on Meta::CPAN

subtest 'Reporter::generate_markdown_report - structure and content' => sub {
	my @issues = (
		{ type => 'performance', severity => 'medium',
		  message => 'No caching', fix => 'Add caching' },
		{ type => 'security',    severity => 'high',
		  message => 'Unpinned action', fix => 'Pin it' },
	);
	my $report = generate_markdown_report(\@issues);

	ok(defined $report,               'report generated without error');
	like($report, qr/# GHGen/,        'report has main heading');
	like($report, qr/Issues found/i,  'report mentions issue count');
	like($report, qr/performance/i,   'report includes performance section');
	like($report, qr/security/i,      'report includes security section');
	like($report, qr/No caching/,     'report contains first issue message');
	like($report, qr/Unpinned action/,'report contains second issue message');

	# With fixes applied the summary should mention them.
	my $with_fixes = generate_markdown_report(\@issues, ['fix1', 'fix2']);
	like($with_fixes, qr/Fixes applied/i, 'fix count appears when fixes provided');

	# No issues -> no "Issues by Category" section needed.
	my $empty = generate_markdown_report([]);
	unlike($empty, qr/Issues by Category/, 'no-issue report omits category section');

	memory_cycle_ok(\$report, 'report string has no circular references');
};

subtest 'Reporter::generate_github_comment - produces GitHub-ready comment' => sub {
	# Zero issues -> celebration message.
	my $empty_comment = generate_github_comment([]);
	like($empty_comment, qr/No issues found/i, 'zero-issue comment says no issues found');

	my @issues = ({
		type     => 'performance',
		severity => 'medium',
		message  => 'No caching',
		file     => 'ci.yml',
	});
	my $comment = generate_github_comment(\@issues);

	ok(defined $comment,              'comment generated');
	like($comment, qr/GHGen/,         'comment references GHGen');
	like($comment, qr/Performance/i,  'comment includes Performance section');
	like($comment, qr/No caching/,    'comment includes issue message');
	like($comment, qr/ci\.yml/,       'comment shows file name from issue hash');

	# With auto-fixes applied the comment should acknowledge them.
	my $fixed_comment = generate_github_comment(\@issues, ['some_fix']);
	like($fixed_comment, qr/fix/i, 'comment with fixes applied mentions fixes');

	memory_cycle_ok(\$comment, 'comment string has no circular references');
};

# ═══════════════════════════════════════════════════════════════════════
# 9.  Logic-reduction proofs (post-refactoring invariant assertions)
# ═══════════════════════════════════════════════════════════════════════

# Proof: every rule in the apply_fixes dispatch table fires exactly once
# when presented with a matching issue, and never fires on a non-matching
# issue of the same type.  This validates the deductive reduction — that
# cyclomatic complexity 8 (elif chain) is equivalent to complexity 1
# (dispatch loop) for every previously-reachable input.
subtest 'Fixer::apply_fixes dispatch — all 8 rules fire independently' => sub {
	my %DISPATCH_RULES = (
		# type => [ message_that_matches, message_that_does_not_match ]
		'performance_caching'   => [
			'performance',
			'No dependency caching found - increases build times and costs',
		],
		'security_unpinned'     => [
			'security',
			'Found 2 action(s) using @master or @main',
		],
		'security_permissions'  => [
			'security',
			'Missing explicit permissions',
		],
		'maintenance_outdated'  => [
			'maintenance',
			'Found 1 outdated action(s)',
		],
		'cost_concurrency'      => [
			'cost',
			'No concurrency group - old runs continue when superseded',
		],
		'cost_triggers'         => [
			'cost',
			'Workflow triggers on all pushes - consider path/branch filters',
		],
		'maintenance_runner'    => [
			'maintenance',
			'Using older runner versions - consider updating',
		],
		'performance_timeout'   => [
			'performance',
			"Job 'build' is missing timeout-minutes",
		],
	);

	for my $rule_name (sort keys %DISPATCH_RULES) {
		my ($type, $message) = @{ $DISPATCH_RULES{$rule_name} };

		# Each rule needs a minimal workflow that each handler can legally modify.
		my $workflow = {
			concurrency => undef,    # needed by add_concurrency guard
			on => [qw(push)],       # needed by add_trigger_filters
			jobs => {
				test => {
					'runs-on' => 'ubuntu-18.04',    # needed by update_runners
					'timeout-minutes' => undef,
					steps => [
						{ uses => 'actions/checkout@v6' },
						{ uses => 'actions/checkout@main' },    # needed by fix_unpinned
						{ uses => 'actions/cache@v3' },         # needed by update_actions
						{ run  => 'npm ci' },                   # needed by add_caching
					],
				},
			},
		};
		delete $workflow->{jobs}{test}{'timeout-minutes'};	# ensure no key



( run in 0.398 second using v1.01-cache-2.11-cpan-d01c6094234 )