Database-BI
view release on metacpan or search on metacpan
t/function.t view on Meta::CPAN
sub quote_identifier { (my $n = $_[1]) =~ s/`/``/g; return "`$n`" }
sub selectall_arrayref {
my $self = shift;
die "SELECT command denied\n" if $self->{denied};
return $self->{rows};
}
sub disconnect { $_[0]{disconnected}++; return 1 }
package Stub::STH;
sub new { my ($c, %a) = @_; return bless { NAME => $a{names} }, $c }
sub execute { return 1 }
}
# Runs $code with DBI->connect answering $dbh (or failing when it is undef)
# and a driver present, so no server and no installed DBD are needed.
my $with_dbi = sub {
my ($dbh, $code) = @_;
require DBI;
local $INC{'DBD/MariaDB.pm'} = __FILE__;
my @calls;
Test::Mockingbird::mock('DBI::connect', sub { shift; push @calls, [ @_ ]; return $dbh });
my $ok = eval { $code->(\@calls); 1 };
my $err = $@;
Test::Mockingbird::unmock('DBI::connect');
croak_rethrow($err) unless $ok;
return;
};
# Purpose: the settings come from configuration but end up in a DSN string,
# so each part is checked before it is spliced in; and nothing in an error
# may repeat the password.
subtest 'DataSource::_mariadb_dsn -- the settings are checked first' => sub {
my $fn = \&Database::BI::Model::DataSource::_mariadb_dsn;
throws_ok { $fn->($DSX{server}, undef) }
qr/\ADataSource: no MariaDB server is called "mock"; add it to mariadb_servers/, 'no settings';
throws_ok { $fn->($DSX{server}, 'database=shop') } qr/no MariaDB server is called/, 'settings that are not a hash';
for my $bad ([ database => 'shop;host=evil' ], [ database => '' ], [ host => 'a b' ],
[ host => 'x;port=1' ], [ port => '33o6' ], [ port => '-1' ]) {
my ($key, $value) = @{$bad};
my %settings = (database => $DSX{database}, $key => $value);
throws_ok { $fn->($DSX{server}, \%settings) }
qr/\ADataSource: MariaDB server "mock" needs a valid $key in mariadb_servers/,
"$key '$value' cannot reach the DSN";
}
require DBI;
local $INC{'DBD/MariaDB.pm'} = __FILE__;
Test::Mockingbird::mock('DBI::connect', sub { die "Access denied for user\n" });
throws_ok { $ds->mariadb_tables($DSX{server}, { database => $DSX{database}, password => $DSX{password} }) }
qr/\ADataSource: cannot connect to MariaDB server "mock": Access denied for user/, 'a refused connection names the server';
throws_ok { $ds->mariadb_tables($DSX{server}, { database => $DSX{database}, password => $DSX{password} }) }
qr/\A(?!.*\Q$DSX{password}\E)/s, 'and never the password';
Test::Mockingbird::unmock('DBI::connect');
};
subtest 'DataSource::_mariadb_dsn -- what connects, without connecting' => sub {
my $fn = \&Database::BI::Model::DataSource::_mariadb_dsn;
my $dbh = Stub::DBH->new;
$with_dbi->($dbh, sub {
my ($calls) = @_;
my @got = $fn->($DSX{server}, { database => $DSX{database}, username => 'u', password => $DSX{password} });
like $got[0], $DSX{dsn_re}, 'a DSN built only from checked parts, with a time-out';
is_deeply [ @got[1, 2] ], [ 'u', $DSX{password} ], 'and the credentials';
is scalar @{$calls}, 0, 'nothing is connected: Database::Join::Catalog and Database::Abstraction do that';
});
};
# Purpose: the driver is optional; without one the reader is told what to
# install rather than seeing a Perl error.
subtest 'DataSource::_mariadb_dsn -- no driver' => sub {
my $fn = \&Database::BI::Model::DataSource::_mariadb_dsn;
local @INC = (sub { my (undef, $file) = @_; die "hidden\n" if $file =~ m{\ADBD/(?:MariaDB|mysql)\.pm\z}; return }, @INC);
local $INC{'DBD/MariaDB.pm'};
local $INC{'DBD/mysql.pm'};
delete $INC{'DBD/MariaDB.pm'};
delete $INC{'DBD/mysql.pm'};
throws_ok { $fn->($DSX{server}, { database => $DSX{database} }) }
qr/\ADataSource: DBD::MariaDB or DBD::mysql must be installed to open MariaDB server "mock"/,
'says which modules';
};
subtest 'DataSource::mariadb_tables -- tables and their columns, in order' => sub {
my $dbh = Stub::DBH->new(rows => [ [ 'b', 'id' ], [ 'b', 'x' ], [ 'a', 'k' ] ]);
$with_dbi->($dbh, sub {
my $got = $ds->mariadb_tables($DSX{server}, { database => $DSX{database} });
is_deeply $got, [ { name => 'b', columns => [qw(id x)] }, { name => 'a', columns => ['k'] } ],
'one entry a table, in the order the server listed them';
is $dbh->{disconnected}, 1, 'the handle is closed';
memory_cycle_ok $got, 'and the list holds no cycle';
});
my $denied = Stub::DBH->new(denied => 1);
$with_dbi->($denied, sub {
local $@ = 'kept';
throws_ok { $ds->mariadb_tables($DSX{server}, { database => $DSX{database} }) }
qr/\ADataSource: cannot connect to MariaDB server "mock": SELECT command denied/, 'a refused query';
is $denied->{disconnected}, 1, 'still closes the handle';
});
throws_ok { $ds->mariadb_tables($DSX{server}, {}) } qr/needs a valid database/, 'bad settings never connect';
};
# Purpose: the constructor is where a bad table or server must be found,
# not at the first read; and a handle opened to find out is always closed.
subtest 'DataSource::_new_from_mariadb -- refusals' => sub {
my $fn = \&Database::BI::Model::DataSource::_new_from_mariadb;
throws_ok { $fn->($ds, { server => $DSX{server} }) } qr/table/i, 'no table';
throws_ok { $fn->($ds, { server => $DSX{server}, table => 'a`b' }) } qr/\Q"a`b"\E|a`b/, 'a table name that could break out of its quotes';
throws_ok { $fn->($ds, { server => '../x', table => $DSX{table}, mariadb => {} }) }
qr/no MariaDB server is called "\.\.\/x"/, 'a server name that is not a name';
my $dbh = Stub::DBH->new(missing => $DSX{table});
$with_dbi->($dbh, sub {
throws_ok { $fn->($ds, { server => $DSX{server}, table => $DSX{table}, mariadb => { database => $DSX{database} } }) }
qr/\ADataSource: cannot read table "orders" on MariaDB server "mock": /, 'a table the server does not have';
is $dbh->{disconnected}, 1, 'and the handle is closed';
});
my $ok = Stub::DBH->new(names => [qw(id amount)], primary => ['id']);
$with_dbi->($ok, sub {
Test::Mockingbird::mock('Database::Abstraction::new', sub { die "no backend\n" });
require Database::Abstraction;
throws_ok { $fn->($ds, { server => $DSX{server}, table => $DSX{table}, mariadb => { database => $DSX{database} } }) }
qr/\ADataSource: failed to initialise database backend for table "orders": no backend/, 'D::A refusing it';
Test::Mockingbird::unmock('Database::Abstraction::new');
});
};
( run in 0.815 second using v1.01-cache-2.11-cpan-3f962061493 )