# vim: set ts=2 sts=2 sw=2 expandtab smarttab: use strict; use warnings; use Test::More 0.96; use File::Spec::Functions qw( catfile ); # core use Try::Tiny; use Test::Fatal; my $mod = 'DBIx::TableLoader::CSV'; eval "require $mod" or die $@; use DBI (); use DBD::Mock (); sub mock_st { my ($session, $re, %opts) = @_; push @$session, { statement => sub { my ($sql, $state) = @_; is_deeply $state->{bound_params}, $opts{bound_params}, 'bound params: ' . join(', ', @{ $opts{bound_params} }) if $opts{bound_params}; return like($sql, $re, "sql matches: $sql"); }, results => [[]], %opts, }; } sub new_csv_loader { my ($file, $opts) = @_; my $dbh = DBI->connect('dbi:Mock:', undef, undef, { RaiseError => 1, PrintError => 0, }); return $mod->new( dbh => $dbh, file => catfile(qw( t data ), $file), %$opts, ); } sub test_csv { my ($desc, $file, $opts, $rows, $error_re) = @_; my ($table) = $file =~ /^(\w+)/; subtest $desc => sub { my $loader = new_csv_loader($file, $opts); isa_ok($loader, $mod); my $session = []; mock_st($session, qr/BEGIN/); mock_st($session, qr/CREATE\s+TABLE\s+"$table"/); foreach my $row ( @$rows ){ mock_st($session, qr/INSERT\s+INTO\s+"$table"/, bound_params => [ @$row ]); } # expect to see COMMIT unless we're expecting an error first # in which case DBIx::TableLoader 1.100 will issue a rollback mock_st($session, $error_re ? qr/ROLLBACK/ : qr/COMMIT/); $loader->{dbh}->{mock_session} = DBD::Mock::Session->new(csv_error => @$session); my $caught; try { # this will validate dbi actions against the mock session $loader->load; } catch { $caught = $_[0]; if( $error_re ){ like $caught, $error_re, 'got expected error'; } else { ok 0, "got unexpected error: $caught"; } }; if( !$caught ){ if( $error_re ){ ok 0, "no error when expected: $error_re"; } else { ok 1, 'no errors'; } } }; } { # instantiation failure will return undef but not die (so we must) like exception { new_csv_loader('example.csv', { csv_opts => { un_known_attr_ibute => 1 } }, ); }, qr/unknown attribute/i, 'caught csv instantion error'; isa_ok try { new_csv_loader('example.csv', { csv_opts => { auto_diag => 2 } }, ); }, $mod, 'csv object created successfully'; isa_ok try { new_csv_loader('example.csv', { csv => Text::CSV->new, csv_opts => { un_known_attr_ibute => 1 } }, ); }, $mod, 'csv object passed in'; } { my $rows = [ ['salty', "french fries", "sizzle"], ['spicy', "red\\", "crackle"], ['sweet', "golden brown", 'crumble'], ]; my $file = 'bad_escape.csv'; test_csv('all good', $file, {}, $rows); # CSV_PP ERROR: 4002 - EIQ - Unescaped ESC in quoted field # CSV_XS ERROR: 2011 - ECR - Characters after end of quoted field @ pos 15 test_csv('insert one then error with auto_diag', $file, { csv_opts => { escape_char => '\\', auto_diag => 2 } }, [ $rows->[0] ], qr/^(# )?CSV\w* ERROR: \d+ - \w+ -/, ); test_csv('insert one then error with our own', $file, { csv_opts => { escape_char => '\\' } }, [ $rows->[0] ], qr/^CSV parse error: /i, ); test_csv('insert one, discard the error, and finish early', $file, { csv_opts => { escape_char => '\\' }, ignore_csv_errors => 1 }, [ $rows->[0] ], ); } { # most DBD's will die by themselves, but Mock will not, so set handle => die like exception { new_csv_loader('alt_sep.txt', { handle_invalid_row => 'die', }, )->load; }, qr/Row has 4 fields when 3 are expected/i, 'alternate separator breaks rows'; test_csv('alternate separator configured properly works', 'alt_sep.txt', { handle_invalid_row => 'die', csv_opts => { sep_char => '|' } }, [ ['1,2,3', '4,5'], ['5,6', '7,8'] ], ); } done_testing;