App-Access2CSV
view release on metacpan or search on metacpan
t/exporter.t view on Meta::CPAN
my $path = shift;
open my $fh, '<:raw', $path or die "$path: $!";
local $/;
return scalar <$fh>;
}
# export(%args): run an exporter over @tables in a fresh directory and
# return (status, output dir, stdout, stderr, logger)
sub export {
my ($tables, %args) = @_;
my $dir = tempdir(CLEANUP => 1);
my $db = make_database($dir, @{$tables});
my $out = File::Spec->catdir($dir, 'out');
my $logger = Local::Logger->new();
my $e = App::Access2CSV::Exporter->new(output_dir => $out, progress => 0, logger => $logger, %args);
my $status;
my ($stdout, $stderr) = capture { $status = $e->run($db) };
return ($status, $out, $stdout, $stderr, $logger, $e, $db);
}
subtest 'constructor validation' => sub {
isa_ok(App::Access2CSV::Exporter->new(), 'App::Access2CSV::Exporter');
isa_ok(App::Access2CSV::Exporter->new({ encoding => 'cp1252' }), 'App::Access2CSV::Exporter', 'hashref form');
lives_ok { App::Access2CSV::Exporter->new(tables => undef) } 'undef means default';
throws_ok { App::Access2CSV::Exporter->new(encoding => 'latin1') } qr/encoding/, 'bad encoding';
throws_ok { App::Access2CSV::Exporter->new(bogus => 1) } qr/Unknown parameter 'bogus'/, 'unknown setting';
throws_ok { App::Access2CSV::Exporter->new(logger => 'file.log') } qr/logger/, 'logger must be an object';
throws_ok { App::Access2CSV::Exporter->new(logger => bless({}, 'Local::NoMethods')) } qr/logger.*debug/, 'logger must have debug/info/warn';
throws_ok { App::Access2CSV::Exporter->new(tables => [['nested']]) } qr/'?tables'? can only contain strings/, 'tables must be strings';
my @tables = ('A');
my $e = App::Access2CSV::Exporter->new(tables => \@tables);
push @tables, 'B';
is_deeply($e->{tables}, ['A'], 'table list is copied');
};
subtest 'exports user tables and skips system tables' => sub {
my ($status, $out, $stdout, $stderr, $logger) = export([qw(Orders Customers MSysObjects USysRibbons ~TMPCLP1)]);
is($status, 0, 'success');
ok(-f File::Spec->catfile($out, 'Customers.csv'), 'Customers.csv');
ok(-f File::Spec->catfile($out, 'Orders.csv'), 'Orders.csv');
ok(!-e File::Spec->catfile($out, 'MSysObjects.csv'), 'MSys* skipped');
ok(!-e File::Spec->catfile($out, 'USysRibbons.csv'), 'USys* skipped');
is(slurp(File::Spec->catfile($out, 'Orders.csv')), qq{"id","name"\n1,"Orders"\n}, 'content');
opendir(my $dh, $out);
is_deeply([sort grep { !/^\.\.?$/ } readdir $dh], ['Customers.csv', 'Orders.csv'], 'no temporary files left');
is($stderr, '', 'no warnings');
like($logger->{lines}[-1], qr/INFO Processed 2 tables, 0 failed/, 'summary logged');
like($logger->{lines}[0], qr/INFO Exported Customers => /, 'export logged');
};
subtest 'new files get normal permissions, not File::Temp 0600' => sub {
plan(skip_all => 'Windows has no Unix permission bits') if $^O eq 'MSWin32';
my $old = umask(022);
my (undef, $out) = export(['Orders']);
umask($old);
is((stat(File::Spec->catfile($out, 'Orders.csv')))[2] & oct(777), oct(644), 'mode 0644 under umask 022');
};
subtest 'progress goes to STDERR' => sub {
my ($status, undef, $stdout, $stderr) = export([qw(A B)], progress => 1);
is($stdout, '', 'nothing on STDOUT');
like($stderr, qr/^\[1\/2\] A\n\[2\/2\] B\n\z/, 'numbered progress');
};
subtest 'encodings' => sub {
my (undef, $out) = export(['Unicode'], encoding => 'utf8');
is(slurp("$out/Unicode.csv"), qq{"id","name"\n1,"Caf\xC3\xA9 \xE2\x82\xAC"\n}, 'utf8 is passed through');
(undef, $out) = export(['Unicode'], encoding => 'utf8-bom');
is(slurp("$out/Unicode.csv"), qq{\xEF\xBB\xBF"id","name"\n1,"Caf\xC3\xA9 \xE2\x82\xAC"\n}, 'utf8-bom adds a BOM, once');
(undef, $out) = export(['Unicode'], encoding => 'cp1252');
is(slurp("$out/Unicode.csv"), qq{"id","name"\n1,"Caf\xE9 \x80"\n}, 'cp1252 is converted');
};
subtest 'cp1252 refuses characters it cannot represent' => sub {
my ($status, $out, undef, $stderr, $logger) = export([qw(Japanese Orders)], encoding => 'cp1252');
is($status, 1, 'failure status');
like($stderr, qr/FAILED: Japanese: Table Japanese, line 2: cannot be represented in cp1252/, 'warned');
ok(!-e "$out/Japanese.csv", 'no partial file');
ok(-e "$out/Orders.csv", 'other tables still exported');
ok((grep { /^WARN FAILED: Japanese/ } @{ $logger->{lines} }), 'failure logged');
};
subtest 'cp1252 reports invalid UTF-8 from mdb-export' => sub {
my ($status, undef, undef, $stderr) = export(['Latin1'], encoding => 'cp1252');
is($status, 1, 'failure status');
like($stderr, qr/Table Latin1, line 2: output of mdb-export is not valid UTF-8/, 'warned');
};
subtest 'mdb-export failures are per table' => sub {
# "Killed" kills itself with a signal, which only Unix has
my @tables = $^O eq 'MSWin32' ? qw(Broken Orders) : qw(Broken Killed Orders);
my ($status, $out, undef, $stderr, $logger) = export(\@tables);
is($status, 1, 'failure status');
like($stderr, qr/FAILED: Broken: mdb-export failed with exit status 1: corrupt table/, 'exit status');
SKIP: {
skip('Windows has no signals', 1) if $^O eq 'MSWin32';
like($stderr, qr/FAILED: Killed: mdb-export was killed by signal \d+/, 'signal');
}
ok(!-e "$out/Broken.csv", 'no partial file');
ok(-e "$out/Orders.csv", 'good table exported');
my ($total, $failed) = (scalar(@tables), scalar(@tables) - 1);
like($logger->{lines}[-1], qr/Processed $total tables, $failed failed/, 'summary');
};
subtest 'existing files' => sub {
my ($status, $out, undef, undef, undef, $e, $db) = export(['Orders']);
is($status, 0, 'first run');
my $stderr;
(undef, $stderr) = capture { $status = $e->run($db) };
is($status, 1, 'second run fails without --overwrite');
like($stderr, qr/Output file already exists: .*Orders\.csv \(use --overwrite/, 'explains why');
$e->{overwrite} = 1;
( run in 0.504 second using v1.01-cache-2.11-cpan-036bef1c656 )