App-Test-Generator

 view release on metacpan or  search on metacpan

t/edge_cases.t  view on Meta::CPAN


	my $dir  = tempdir(CLEANUP => 1);
	my $file = File::Spec->catfile($dir, 'circular.yml');

	lives_ok {
		$exporter->export(\%plan, $file);
	} 'export() does not crash or hang on a circular-reference plan';

	ok(-s $file, 'a non-empty YAML file was written for the circular structure');
};

# ==================================================================
# TestStrategy::generate_plan -- malformed per-method schema entries
#
# generate_plan() has no schema-shape validation of its own, unlike
# every Planner::* submodule (which all croak on a non-hashref $schema
# argument). Probe both ends of that gap: an undef per-method entry is
# safe (a chained rvalue dereference degrades to defaults), while a
# non-hashref entry such as a plain string is unsafe (dereferencing a
# string as a hashref dies under "strict refs"). Both behaviours were
# verified directly against the running code before being asserted
# here.
# ==================================================================
subtest 'TestStrategy::generate_plan: undef per-method entry degrades to the basic_test fallback' => sub {
	my $strategy = App::Test::Generator::TestStrategy->new(
		schema => { mystery_method => undef },
	);

	my $plan;
	lives_ok {
		$plan = $strategy->generate_plan;
	} 'generate_plan() lives when a per-method schema entry is undef';

	is_deeply(
		$plan->{mystery_method},
		{ basic_test => 1 },
		'an undef per-method entry degrades to the basic_test-only fallback plan',
	);
};

subtest 'TestStrategy::generate_plan: non-hashref per-method entry dies with a clear type error' => sub {
	my $strategy = App::Test::Generator::TestStrategy->new(
		schema => { broken_method => 'not a hashref' },
	);

	throws_ok {
		$strategy->generate_plan;
	} qr/HASH ref/,
		'a non-hashref per-method schema entry dies clearly rather than silently misbehaving';
};

# ==================================================================
# Generator::generate() -- end-to-end injection attempt via a
# schema-derived function name
#
# _assert_identifier() itself is already unit-tested directly
# elsewhere (t/function.t). This instead exercises the *full*
# generate() pipeline end to end: a function name shaped like a Perl
# statement-injection payload (semicolon-separated, containing a
# system() call) must be rejected before it ever reaches the point of
# being spliced unescaped into generated test source, and critically,
# before any output file is created on disk.
# ==================================================================
subtest 'Generator::generate(): statement-injection-shaped function name is rejected before any file is written' => sub {
	my $dir     = tempdir(CLEANUP => 1);
	my $outfile = File::Spec->catfile($dir, 'out.t');

	my $schema = {
		function => 'evil; system("touch /tmp/pwned"); 1',
		input    => { number => { type => 'number', position => 0 } },
		output   => { type => 'number' },
	};

	throws_ok {
		App::Test::Generator->generate(schema => $schema, output_file => $outfile);
	} qr/not a valid Perl identifier/,
		'a statement-injection-shaped function name is rejected';

	ok(!-e $outfile, 'no output file was written once the malicious identifier was rejected');
};

subtest '_compile_signature_isolated: pathological subprocess outputs are handled gracefully' => sub {
	# Covers three hostile subprocess scenarios:
	#   1. SIGKILL before any write  — stdout empty, stderr empty
	#   2. Corrupt/truncated output  — stdout non-JSON, stderr empty
	#   3. Only stderr output        — stdout empty, stderr has message
	#
	# In every case the parent must return undef rather than croaking with
	# "malformed JSON string" (regression: the croak used to propagate through
	# Test::Builder's subtest boundary and exit the process with code 255).
	#
	# Strategy: mock open3 in the consuming namespace so no real subprocess
	# is spawned; waitpid($$, 0) on our own PID returns -1 harmlessly.

	my $make_extractor = sub {
		bless { allow_signature_exec => 1, verbose => 0 },
			'App::Test::Generator::SchemaExtractor';
	};

	my $mock_open3 = sub {
		my ($stdout_content, $stderr_content) = @_;
		open my $fake_rdr, '<', \$stdout_content or die;
		open my $fake_err, '<', \$stderr_content or die;
		no warnings 'redefine';
		local *App::Test::Generator::SchemaExtractor::open3 = sub {
			$_[0] = do { open my $fh, '>', \my $buf; $fh };
			$_[1] = $fake_rdr;
			$_[2] = $fake_err;
			return $$;
		};
		return;
	};

	{
		my $empty = '';
		open my $fake_rdr, '<', \$empty or die;
		open my $fake_err, '<', \$empty or die;
		no warnings 'redefine';
		local *App::Test::Generator::SchemaExtractor::open3 = sub {
			$_[0] = do { open my $fh, '>', \my $buf; $fh };
			$_[1] = $fake_rdr;



( run in 1.654 second using v1.01-cache-2.11-cpan-788537b7465 )