App-Access2CSV

 view release on metacpan or  search on metacpan

t/function.t  view on Meta::CPAN


	is($buffer, "\"name\"\n\"Caf\xE9 \x80\"\n", 'e-acute and Euro converted');
	is_deeply(\@calls, [[$CONFIG{mdb_export}, ['--', $CONFIG{database}, $CONFIG{table}]]], 'mdb-export arguments, options ended by --');
};

subtest 'Exporter::_export_transcoded reports bad input with line numbers' => sub {
	my $dir = tempdir(CLEANUP => 1);
	open my $out, '>:raw', \my $buffer or die $!;

	{
		my $guard = mock_scoped(mock_export_output("ok\nCaf\xE9\n"));
		throws_ok { new_exporter(output_dir => $dir)->_export_transcoded($CONFIG{database}, $CONFIG{table}, $out) }
			qr/\ATable $CONFIG{table}, line 2: output of mdb-export is not valid UTF-8 at /, 'invalid UTF-8';
	}
	{
		my $guard = mock_scoped(mock_export_output("ok\nok\n\xE6\x97\xA5\n"));
		throws_ok { new_exporter(output_dir => $dir)->_export_transcoded($CONFIG{database}, $CONFIG{table}, $out) }
			qr/\ATable $CONFIG{table}, line 3: cannot be represented in cp1252 at /, 'unmappable character';
	}
};

subtest 'Exporter::_export_transcoded leaves the caller\'s $. and $@ alone' => sub {
	# Reading the spool file must not change which handle $. refers to
	open my $in, '<', \"one\ntwo\nthree\n" or die $!;
	<$in> for 1 .. 2;
	my $before = $.;

	my $guard = mock_scoped(mock_export_output("a\nb\nc\nd\n"));
	open my $out, '>:raw', \my $buffer or die $!;
	my $e = new_exporter(output_dir => tempdir(CLEANUP => 1));
	local $@ = $CONFIG{sentinel};
	$e->_export_transcoded($CONFIG{database}, $CONFIG{table}, $out);

	is($., $before, '$. still refers to the caller\'s handle');
	is($@, $CONFIG{sentinel}, '$@ localised');
};

subtest 'Exporter::_install_file renames into place with normal permissions' => sub {
	my $dir = tempdir(CLEANUP => 1);
	my $outfile = File::Spec->catfile($dir, 'out.csv');
	my $e = new_exporter(output_dir => $dir);

	my $old_umask = umask(oct(22));
	{
		my $tmp = File::Temp->new(DIR => $dir, UNLINK => 1);
		print {$tmp} "data\n";
		local $@ = $CONFIG{sentinel};
		is($e->_install_file($tmp, $outfile), $e, 'returns $self');
		is($@, $CONFIG{sentinel}, '$@ localised');
		ok(!-e $tmp->filename(), 'temporary name gone');
	}
	umask($old_umask);

	ok(-f $outfile, 'still there after the temporary object is destroyed');

	# Whatever the platform, the result is an ordinary file the user can
	# read and write (File::Temp's own files are private, mode 0600)
	ok(-r $outfile && -w $outfile, 'readable and writable');

	SKIP: {
		# Windows has no Unix permission bits: stat() makes the mode up from
		# the read-only flag (0666 for any writable file) and umask has no
		# effect, so the exact bits can only be checked elsewhere
		skip('Unix permission bits do not exist on Windows', 1) if $^O eq 'MSWin32';
		is((stat($outfile))[2] & oct(777), oct(644), '0666 less umask 022');
	}
};

subtest 'Exporter::_install_file croaks with the OS reason on failure' => sub {
	my $dir = tempdir(CLEANUP => 1);
	my $outfile = File::Spec->catfile($dir, 'no', 'such', 'dir', 'out.csv');
	my $tmp = File::Temp->new(DIR => $dir, UNLINK => 1);

	throws_ok { new_exporter()->_install_file($tmp, $outfile) } qr/\ACannot write \Q$outfile\E: \Q$ENOENT_TEXT\E at /, 'exact message';
};

subtest 'Exporter::_count_rows reads the number from mdb-count' => sub {
	my ($output, @calls) = ("  $CONFIG{row_count}\n");
	my $guard = mock_scoped("$CONFIG{exporter}::_run_program" => sub {
		push @calls, [$_[1], $_[2]];
		${ $_[3] } = $output;
		return $_[0];
	});

	my $rows = new_exporter()->_count_rows($CONFIG{database}, $CONFIG{table});
	is($rows, $CONFIG{row_count}, 'number parsed despite whitespace');
	returns_ok($rows, { type => 'integer', min => 0 }, 'an integer');
	is_deeply(\@calls, [[$CONFIG{mdb_count}, ['--', $CONFIG{database}, $CONFIG{table}]]], 'mdb-count arguments, options ended by --');

	# Anything that is not just a number is reported, never taken as 0
	$output = '';
	throws_ok { new_exporter()->_count_rows($CONFIG{database}, $CONFIG{table}) } qr/\Amdb-count printed no number: "" at /, 'no output: reported';
	$output = "count: 12 rows\n";
	throws_ok { new_exporter()->_count_rows($CONFIG{database}, $CONFIG{table}) } qr/\Amdb-count printed no number: "count: 12 rows\\x0A" at /, 'extra text: reported';
};

# Replaces run3 with a double that records its arguments, writes to
# stderr and sets the child status
sub mock_run3 {
	my ($status, $stderr_text, $calls) = @_;
	return ("$CONFIG{exporter}::run3" => sub {
		my ($cmd, $stdin, $stdout, $stderr) = @_;
		push @{$calls}, [$cmd, $stdin, $stdout] if $calls;
		${$stderr} = $stderr_text;
		$? = $status;
		return 1;
	});
}

subtest 'Exporter::_run_program runs the program with a list, not a shell string' => sub {
	my @calls;
	my $guard = mock_scoped(mock_run3(0, '', \@calls));

	my $e = new_exporter();
	$e->{programs} = { $CONFIG{mdb_export} => program_path($CONFIG{mdb_export}) };
	my $stdout = '';
	is($e->_run_program($CONFIG{mdb_export}, [$CONFIG{database}, 'Or; rm -rf /'], \$stdout), $e, 'returns $self');
	verbose_diag('run3 call', \@calls);

	is_deeply($calls[0][0], [program_path($CONFIG{mdb_export}), $CONFIG{database}, 'Or; rm -rf /'], 'command as a list');
	is(${ $calls[0][1] }, undef, 'stdin is empty');
	is($calls[0][2], \$stdout, 'stdout passed through');
};

subtest 'Exporter::_run_program croaks on exit status and signals' => sub {



( run in 0.512 second using v1.01-cache-2.11-cpan-036bef1c656 )