App-Test-Generator
view release on metacpan or search on metacpan
t/edge_cases.t view on Meta::CPAN
subtest 'render_args_hash: handles Regexp values' => sub {
my $result = App::Test::Generator::render_args_hash({
matches => qr/^\d+$/,
type => 'string',
});
ok(defined $result, 'Regexp in args hash handled');
like($result, qr/qr\{/, 'Regexp rendered in args hash');
};
subtest 'render_hash: skips undef sub-values gracefully' => sub {
my $result = App::Test::Generator::render_hash({
param => { type => 'string', min => undef, max => 10 }
});
ok(defined $result, 'undef sub-value handled');
unlike($result, qr/min/, 'undef min key omitted');
like($result, qr/max/, 'defined max key present');
};
# ==================================================================
# Planner::Isolation -- asymmetric argument validation
#
# plan() croaks if $strategy is not a hashref, but never validates
# $schema at all. Every access to $schema below is a chained rvalue
# dereference ($schema->{$method}{_analysis}...), which Perl resolves
# to undef without autovivifying or dying even when $schema itself is
# undef. This is real, intentional asymmetry (CLAUDE.md documents it),
# not an oversight -- the hostile case to prove is that a caller who
# forgets to pass $schema still gets a safe, maximally-defensive plan
# rather than a crash.
# ==================================================================
subtest 'Planner::Isolation::plan: undef schema degrades gracefully to the safest fixture' => sub {
my $planner = App::Test::Generator::Planner::Isolation->new();
my $isolation;
lives_ok {
$isolation = $planner->plan(undef, { risky_method => 1 });
} 'plan() does not die when $schema is undef';
is_deeply(
$isolation,
{ risky_method => { fixture => 'isolated_block' } },
'undef schema degrades to the most defensive (isolated_block) fixture, not a crash',
);
};
# ==================================================================
# Model::Method -- aliasing/state abuse via evidence_ref()
#
# POD documents evidence_ref() as returning "the live internal
# arrayref, not a copy". Treated as a hostile capability: a caller can
# splice a forged entry directly into that arrayref, completely
# bypassing add_evidence()'s category/signal whitelist validation, and
# have it silently summed by resolve_confidence() anyway, since that
# method trusts every entry's weight regardless of provenance.
# ==================================================================
subtest 'Model::Method::evidence_ref: external mutation bypasses add_evidence() validation entirely' => sub {
my $m = App::Test::Generator::Model::Method->new(name => 'm', source => 'sub m {}');
my $ref = $m->evidence_ref;
push @{$ref}, {
category => 'not_a_real_category',
signal => 'not_a_real_signal',
weight => $FORGED_EVIDENCE_WEIGHT,
};
is(scalar($m->evidence), 1,
'forged entry is visible through evidence() despite never passing through add_evidence()');
my $conf = $m->resolve_confidence;
is($conf->{score}, $FORGED_EVIDENCE_WEIGHT,
'forged weight is summed into resolve_confidence() with no provenance check');
is($conf->{level}, 'high',
'a single forged entry is enough on its own to reach "high" confidence');
};
# ==================================================================
# Model::Method -- list vs scalar context confusion
#
# evidence() is documented to behave differently depending on calling
# context: list context yields every evidence hashref, scalar context
# yields only the count (via Perl's own "array in scalar context is
# its length" rule, not a special case in the sub body). Exercise both
# conventions side by side on the same populated object.
# ==================================================================
subtest 'Model::Method::evidence: list and scalar context return genuinely different things' => sub {
my $m = App::Test::Generator::Model::Method->new(name => 'm', source => 'sub m {}');
$m->add_evidence(category => 'return', signal => 'returns_constant', weight => 10);
$m->add_evidence(category => 'return', signal => 'returns_self', weight => 15);
my @list_ctx = $m->evidence;
my $scalar_ctx = $m->evidence;
is(scalar(@list_ctx), 2, 'list context returns both evidence hashrefs');
is($scalar_ctx, 2, 'scalar context returns the entry count, not the last entry');
is(ref($list_ctx[0]), 'HASH', 'each list-context element is still a real evidence hashref');
};
# ==================================================================
# Model::Method -- unlocalized $_ mutation
#
# resolve_confidence() sums weights via the postfix form
# "$total += $_->{weight} for @{ $self->{evidence} };". Postfix
# for/foreach always aliases and restores $_ around the loop body, but
# that safety property is easy to break if the loop is ever rewritten
# -- assert it explicitly rather than trusting it silently continues
# to hold under future refactors.
# ==================================================================
subtest 'Model::Method::resolve_confidence: does not leak $_ into the caller' => sub {
local $_ = 'sentinel-before-call';
my $m = App::Test::Generator::Model::Method->new(name => 'm', source => 'sub m {}');
$m->add_evidence(category => 'return', signal => 'returns_constant', weight => 10);
$m->resolve_confidence;
is($_, 'sentinel-before-call',
'$_ is unchanged by resolve_confidence(), confirming the postfix for-loop restores it');
};
# ==================================================================
# Analyzer::SideEffect / Analyzer::Complexity -- unexpected reference
# types in place of the documented source-string body
#
( run in 1.820 second using v1.01-cache-2.11-cpan-9581c071862 )