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 )