DBIO
view release on metacpan or search on metacpan
t/storage/async_access_broker.t view on Meta::CPAN
# ABSTRACT: Storage::Async AccessBroker consumption seam (mock only, no real DB)
use strict;
use warnings;
use Test::More;
BEGIN { eval { require Future; 1 } or plan skip_all => 'Future not installed' }
use DBIO::Storage::Async;
use DBIO::Storage::PoolBase;
# A rotating credential source: each call yields a freshly-numbered
# storage-native conninfo hashref, so we can prove the pool pulls fresh
# credentials per spawn rather than reusing a snapshot.
{
package TestBroker;
use base 'DBIO::AccessBroker';
sub new { bless { calls => 0 }, shift }
# Async storages call current_connect_info_for_storage, which routes
# through connect_info_for_storage. Return storage-native shape.
sub connect_info_for_storage {
my ($self, $storage, $mode) = @_;
my $n = ++$self->{calls};
return { dbname => 'app', user => "user_$n" };
}
}
# Minimal mock pool: records the (transformed) conninfo it built each
# connection from, and never touches a real database.
{
package MockPool;
use base 'DBIO::Storage::PoolBase';
sub _create_connection {
my ($self, $conninfo) = @_;
push @{ $self->{_built_from} ||= [] }, $conninfo;
return bless { conninfo => $conninfo }, 'MockConn';
}
# Echo the storage-native value through unchanged so the test can read
# exactly what the provider handed the pool per spawn.
sub _transform_conninfo { $_[1] }
}
# Minimal async storage consuming the lifted seam. It supplies only the
# driver-specific seam hook (_async_broker_conninfo) and a pool that wires
# in the inherited _conninfo_provider.
{
package MockAsyncStorage;
use base 'DBIO::Storage::Async';
sub new {
my ($class, $schema) = @_;
bless { schema => $schema, _conninfo_provider => undef }, $class;
}
sub future_class { 'Future' }
sub connect_info {
my ($self, $info) = @_;
if ($info) {
$self->{connect_info} = $info;
if ($self->_is_access_broker_connect_info($info)) {
$self->_setup_access_broker($info->[0]);
}
else {
$self->_clear_access_broker;
$self->{_static_conninfo} = $info;
}
}
return $self->{connect_info};
}
# The one storage-native seam hook: one fresh conninfo value per spawn.
sub _async_broker_conninfo {
my ($self, $mode) = @_;
return $self->current_access_broker_connect_info($mode);
}
sub pool {
my $self = shift;
$self->{pool} ||= do {
my %args = (size => 5);
if (my $provider = $self->_conninfo_provider) {
$args{conninfo_provider} = $provider;
}
else {
$args{conninfo} = $self->{_static_conninfo};
}
MockPool->new(%args);
};
}
}
# --- broker detection is inherited ---
my $storage = MockAsyncStorage->new(undef);
ok $storage->_is_access_broker_connect_info([ TestBroker->new ]),
'single-element arrayref of a broker is detected';
ok !$storage->_is_access_broker_connect_info(['dbi:Pg:dbname=app']),
'plain connect info is not a broker';
ok !$storage->_is_access_broker_connect_info([ TestBroker->new, {} ]),
'two-element arrayref is not a broker invocation';
# --- wiring a broker installs the per-spawn provider ---
my $broker = TestBroker->new;
$storage->connect_info([ $broker ]);
is $storage->access_broker, $broker, 'broker attached via inherited set_access_broker';
ok $storage->_conninfo_provider, 'conninfo_provider installed by _setup_access_broker';
is ref($storage->_conninfo_provider), 'CODE', 'provider is a coderef';
# --- every NEW pool connection gets fresh credentials ---
my $pool = $storage->pool;
isa_ok $pool, 'DBIO::Storage::PoolBase', 'pool';
my $c1 = $pool->acquire->get;
my $c2 = $pool->acquire->get;
my $c3 = $pool->acquire->get;
is_deeply $c1->{conninfo}, { dbname => 'app', user => 'user_1' },
'first spawn pulled fresh credentials via the inherited seam';
is_deeply $c2->{conninfo}, { dbname => 'app', user => 'user_2' },
'second spawn pulled freshly-refreshed credentials';
is_deeply $c3->{conninfo}, { dbname => 'app', user => 'user_3' },
'third spawn pulled freshly-refreshed credentials again';
is $broker->{calls}, 3, 'broker consulted once per pool spawn, not snapshotted';
# --- detaching the broker tears the provider down ---
my $plain = MockAsyncStorage->new(undef);
$plain->connect_info([ $broker ]);
ok $plain->_conninfo_provider, 'provider present while broker attached';
$plain->connect_info(['dbi:Pg:dbname=app']);
ok !$plain->access_broker, 'broker cleared on non-broker connect_info';
ok !$plain->_conninfo_provider, 'provider torn down on non-broker connect_info';
# --- the seam hook is required when a driver does not override it ---
{
package BareAsync;
use base 'DBIO::Storage::Async';
sub future_class { 'Future' }
}
my $bare = bless {}, 'BareAsync';
eval { $bare->_async_broker_conninfo('write') };
like $@, qr/Subclass must override _async_broker_conninfo/,
'_async_broker_conninfo croaks until a driver implements it';
done_testing;
( run in 1.580 second using v1.01-cache-2.11-cpan-f52f0507bed )