App-makefilepl2cpanfile

 view release on metacpan or  search on metacpan

t/edge_cases.t  view on Meta::CPAN

			with_develop => 0,
		)
	} 'path with spaces does not trigger shell-quoting issues';

	like $out, qr/Carp/, 'correct output from path with spaces';
};

subtest 'generate: path with shell-injection characters is rejected by Cannot-read guard' => sub {
	# Paths like "/tmp; rm -rf /" contain shell metacharacters, but since
	# Perl and Path::Tiny never pass them to a shell, they are benign file
	# paths.  No such file exists, so the guard croaks safely.
	my $g = empty_home();

	throws_ok {
		App::makefilepl2cpanfile::generate(makefile => '/tmp/; rm -rf /; #.pl')
	} qr/Cannot read/, 'shell-injection path is rejected (file does not exist)';

	throws_ok {
		App::makefilepl2cpanfile::generate(makefile => "/tmp/Make\nfile.PL")
	} qr/Cannot read/, 'path with embedded newline is rejected (file does not exist)';
};

subtest 'generate: extremely long path is rejected by Cannot-read guard' => sub {
	# Most filesystems cap path lengths well below PATH_MAX (4096 on Linux).
	# A 5000-char path cannot correspond to an existing file; the guard must
	# croak cleanly without crashing the regex or OS call.
	my $g         = empty_home();
	my $long_path = 'M' x 5000;

	throws_ok {
		App::makefilepl2cpanfile::generate(makefile => $long_path)
	} qr/Cannot read/, 'extremely long path causes croak gracefully';
};

# -----------------------------------------------------------------------
# SECTION 7: Upstream Failure Simulation via Test::Mockingbird
#
# These tests simulate real-world upstream failures: I/O interruptions,
# missing home directory (containers), and YAML service timeouts.
# They verify that the module propagates errors clearly without masking
# the root cause or leaving the process in a corrupted state.
# -----------------------------------------------------------------------

subtest 'upstream: Path::Tiny::slurp_utf8 dies — generate propagates the I/O error' => sub {
	# Simulates a network filesystem going down between the readability check
	# and the actual read, or a catastrophic hardware read error.
	# Strategy: create a valid Makefile.PL (passes -f && -r), then mock
	# slurp_utf8 to die so the error surfaces after the guard.
	my $g_home  = empty_home();
	my $mf      = make_mf($MF_SIMPLE);
	my $g_slurp = mock_scoped 'Path::Tiny::slurp_utf8' => sub {
		die "Input/output error: simulated disk failure\n";
	};

	throws_ok {
		App::makefilepl2cpanfile::generate(
			makefile     => "$mf",
			with_develop => 0,
		)
	} qr/Input\/output error.*simulated disk failure/,
		'slurp_utf8 I/O failure propagates transparently from generate()';
};

subtest 'upstream: File::HomeDir::my_home returns undef — falls back to defaults (BUG 3)' => sub {
	# Simulates a container or CI job that runs under a user with no home
	# directory set (e.g., nobody, or a UID with no passwd entry).
	# BUG 3 (fixed): before the fix, path(undef) croaked from Path::Tiny
	# with "positive-length parts" error, not a clean fallback to defaults.
	my $g_home = mock_scoped 'File::HomeDir::my_home' => sub { undef };
	my $mf     = make_mf($MF_SIMPLE);

	my $out;
	lives_ok {
		$out = App::makefilepl2cpanfile::generate(
			makefile     => "$mf",
			with_develop => 1,    # forces _load_develop_config call
		)
	} 'undef my_home does not crash (BUG 3 fixed — falls back to defaults)';

	like $out, qr/Perl::Critic/,
		'default develop tool present — fallback to %DEFAULT_DEVELOP works';
	like $out, qr/on 'develop' => sub/,
		'develop block was emitted using default tools';

	diag "Output:\n$out" if $ENV{TEST_VERBOSE};
};

subtest 'upstream: YAML::Tiny simulated timeout — croak includes path and message' => sub {
	# Simulates a YAML parsing backend that fails mid-operation (e.g. a remote
	# config service returning an error, or a corrupt/truncated YAML file).
	# The croak message must include both the config file path and the upstream
	# error text so operators can diagnose the failure.
	my $dir = tempdir(CLEANUP => 1);
	path($dir)->child('.config')->mkpath;
	path($dir)->child('.config', 'makefilepl2cpanfile.yml')->spew_utf8(
		"develop:\n  Perl::Critic: 0\n"    # valid YAML on disk
	);

	my $g_home = mock_scoped 'File::HomeDir::my_home' => sub { $dir };
	my $g_yaml = mock_scoped(
		'YAML::Tiny::read'   => sub { undef },
		'YAML::Tiny::errstr' => sub { 'connection timed out after 30s' },
	);

	my $mf = make_mf($MF_SIMPLE);

	throws_ok {
		App::makefilepl2cpanfile::generate(
			makefile     => "$mf",
			with_develop => 1,
		)
	} qr/Failed to parse .+ connection timed out/s,
		'YAML upstream failure croaks with path and error message';
};

subtest 'upstream: Path::Tiny::is_file dies (simulated stat() failure) — propagates' => sub {
	# Simulates a filesystem that has become unreachable after the home
	# directory was resolved (e.g. an NFS mount that went stale between
	# the my_home() call and the subsequent is_file() check on the config path).
	# The error must propagate out of generate() without being swallowed.
	my $g_home   = empty_home();



( run in 3.069 seconds using v1.01-cache-2.11-cpan-354807fb38d )