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 )