The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
# vim: set ts=2 sts=2 sw=2 expandtab smarttab:
use strict;
use warnings;
use Test::More 0.96;
use Test::MockObject 1.09 ();
use File::Spec::Functions qw( catfile ); # core
use Symbol; # core

my $mod = 'DBIx::TableLoader::CSV';
eval "require $mod" or die $@;

our $csv_class;
our %csv_classes = (map { my $e = eval "require $_"; $_ => $e } qw(Text::CSV Text::CSV_XS Text::CSV_PP));

sub test_with_all_classes ($$);

# get_raw_row()
test_with_all_classes get_raw_row => sub
{
  my $loader = new_ok($mod, [io => new_io(), default_column_type => 'foo', csv_class => $csv_class]);
  # columns determined from first row
  is_deeply($loader->columns, [[qw(fld1 foo)], [qw(fld2 foo)]], 'columns');
  is_deeply($loader->column_names, [qw(fld1 fld2)], 'column names');
  # get_row calls get_raw_row
  is_deeply($loader->get_raw_row, [qw(row1 col2)],     'raw row 1');
  is_deeply($loader->get_row,     [qw(row2 col2)],     'row 2');
  is_deeply($loader->get_raw_row, [qw(row3), "col 2"], 'raw row 3');
};

# default_name()
test_with_all_classes default_name => sub
{
  # we're basically testing File::Basename isn't necessary
  foreach my $test (
    ['/tmp/goober.csv' => 'goober'],
    ['har de har har.fudge.csv' => 'har de har har.fudge'],
    ['ahoy.tab' => 'ahoy'],
    ['' => 'csv'],
  ){
    my ($file, $exp) = @$test;
    is(new_ok($mod, [io => new_io(), file => $file, csv_class => $csv_class])->default_name, $exp, 'default_name');
  }
};

# prepare_data() options
test_with_all_classes 'prepare_data() options' => sub
{
  my $loader;
  my $mock = Test::MockObject->new();
  $mock->fake_module('Fake_CSV',
    new => sub { bless( ($_[1]||{}), $_[0] ) },
    getline => sub { [1] }
  );

  # csv
  $loader = new_ok($mod, [io => new_io(), csv_class => $csv_class]);
  isa_ok($loader->{csv}, $csv_class);
  my $csv = Fake_CSV->new({goo => 'ber'});
  $loader = new_ok($mod, [io => new_io(), csv => $csv]);
  is($loader->{csv}, $csv, 'csv option');
  is($loader->{csv}->{goo}, 'ber', 'csv option');

  # csv_class
  $loader = new_ok($mod, [io => new_io(), csv_class => $csv_class]);
  isa_ok($loader->{csv}, $csv_class);
  $loader = new_ok($mod, [io => new_io(), csv_class => 'Fake_CSV']);
  isa_ok($loader->{csv}, 'Fake_CSV');

  # csv_opts, csv_defaults
  $loader = new_ok($mod, [io => new_io(), csv_class => 'Fake_CSV']);
  is($loader->{csv}->{binary}, 1, 'csv_defaults');
  $loader = new_ok($mod, [io => new_io(), csv_class => 'Fake_CSV', csv_opts => {binary => 12}]);
  is($loader->{csv}->{binary}, 12, 'csv_defaults overridden with csv_opts');
  $loader = new_ok($mod, [io => new_io(), csv_class => 'Fake_CSV', csv_defaults => {}]);
  ok(!exists($loader->{csv}->{binary}), 'csv_defaults emptied');

  # file (no io)
  is(eval { $mod->new(file => '') }, undef, 'die w/o file');
  like($@, qr/Cannot proceed without/, 'no file error');
  is(eval { $mod->new(file => catfile('t', 'file that does', 'not.exist')) }, undef, 'die w/o file');
  like($@, qr/Failed to open/, 'cannot find file');

  $loader = eval { $mod->new(file => catfile(qw( t data example.csv ))) };
  is($@, '', 'no error');
  isa_ok($loader, $mod, 'file exists');

  # no_header
  $loader = new_ok($mod, [io => new_io()]);
  is_deeply($loader->column_names, [qw(fld1 fld2)], 'csv 1st row column names');
  is_deeply($loader->get_raw_row, [qw(row1 col2)],  'csv 2nd row is 1st row of data');

  $loader = new_ok($mod, [io => new_io(), columns => [qw(goo ber)]]);
  is_deeply($loader->column_names, [qw(goo ber)],   'provided column names');
  is_deeply($loader->get_raw_row, [qw(row1 col2)],  'csv 2nd row is 1st row of data');

  $loader = new_ok($mod, [io => new_io(), columns => [qw(goo ber)], no_header => 1]);
  is_deeply($loader->column_names, [qw(goo ber)],   'provided column names');
  is_deeply($loader->get_raw_row, [qw(fld1 fld2)],  'no_header option returns 1st row of csv in 2st row of data');
};

done_testing;

sub new_io {
  # use fake io to avoid opening files
  my $data = ["fld1,fld2\n", "row1,col2\n", "row2,col2\n", qq[row3,"col 2"\n]];
  return Test::MockObject->new(
    # Text::CSV_PP calls eof() which requires a Glob reference
    Symbol::gensym()
  )->mock(getline => sub { shift @$data });
}

sub test_with_all_classes ($$) {
  my ($name, $subtest) = @_;
  foreach my $class ( keys %csv_classes ){
    local $csv_class = $class;
    subtest "$name with $csv_class" => sub {
      plan skip_all => "$csv_class required for testing with it"
        if ! $csv_classes{$csv_class};

      $subtest->();
    };
  }
}