DBIO

 view release on metacpan or  search on metacpan

t/test/15_async_orchestration.t  view on Meta::CPAN

  sub acquire_txn { Test::SyncFuture->done('CONN') }
  sub release     { push @{ $_[0]->{released} }, $_[1]; 1 }
  sub available   { $_[0]->{available} }
  sub shutdown    { $_[0]->{available} = 0 }
}

# --- The fake transport backend --------------------------------------------
# A concrete DBIO::Storage::Async that overrides ONLY the seam hooks, all
# resolving immediately. Every query the orchestration emits is captured, and
# the rows the transport "returns" are settable per call via {next_rows}.
{
  package Test::SyncBackend;
  use base 'DBIO::Storage::Async';

  sub new {
    my ($class, $schema) = @_;
    my $self = $class->SUPER::new($schema);
    $self->{captured}  = [];
    $self->{next_rows} = [];
    return $self;
  }

  # seam: Future implementation
  sub future_class { 'Test::SyncFuture' }

  # seam: SQL maker
  sub sql_maker_class { 'DBIO::SQLMaker' }
  sub _sql_maker_args { (quote_char => '"', name_sep => '.') }

  # seam: SQL shaping (identity transform, PG-style RETURNING)
  sub _transform_sql   { $_[1] }
  sub _post_insert_sql { ' RETURNING *' }

  # seam: pool
  sub pool { $_[0]->{pool} ||= Test::SyncPool->new }

  # seam: query transport -- capture and resolve immediately
  sub _query_async {
    my ($self, $sql, $bind) = @_;
    push @{ $self->{captured} }, { sql => $sql, bind => $bind, pinned => 0 };
    return $self->future_class->done(@{ $self->{next_rows} });
  }

  sub _query_async_pinned {
    my ($self, $conn, $sql, $bind) = @_;
    push @{ $self->{captured} }, { sql => $sql, bind => $bind, pinned => 1, conn => $conn };
    return $self->future_class->done(@{ $self->{next_rows} });
  }

  # seam: pipeline bracketing
  sub _pipeline_enter { push @{ $_[0]->{captured} }, { sql => 'PIPELINE_ENTER' }; 1 }
  sub _pipeline_exit  { push @{ $_[0]->{captured} }, { sql => 'PIPELINE_EXIT'  }; 1 }
  sub _pipeline_sync  { push @{ $_[0]->{captured} }, { sql => 'PIPELINE_SYNC'  }; $_[0]->future_class->done }

  # test helper
  sub _last_sql { $_[0]->{captured}[-1]{sql} }
}

sub new_backend {
  my $schema = DBIO::Test->init_schema;
  # keep the schema alive for the caller (backend weakens its ref)
  my $backend = Test::SyncBackend->new($schema);
  return ($backend, $schema);
}

# ---------------------------------------------------------------------------
# connect_info normalization
# ---------------------------------------------------------------------------
{
  my ($backend) = new_backend();

  my $ci = [ { host => 'localhost', dbname => 'test', pool_size => 3 }, { RaiseError => 1 } ];
  is_deeply $backend->connect_info($ci), $ci,
    'connect_info returns the stored raw connect info';

  is $backend->{_pool_size}, 3, 'pool_size extracted from conninfo';
  is_deeply $backend->{_conninfo}, { host => 'localhost', dbname => 'test' },
    'conninfo copied with pool_size stripped';
  is_deeply $backend->{_opts}, { RaiseError => 1 }, 'opts copied through';
  is_deeply $ci->[0], { host => 'localhost', dbname => 'test', pool_size => 3 },
    'caller conninfo untouched (normalization works on a copy)';

  # default pool size + default opts
  my ($b2) = new_backend();
  $b2->connect_info([ { host => 'h' } ]);
  is $b2->{_pool_size}, 5, 'pool_size defaults to 5';
  is_deeply $b2->{_opts}, {}, 'opts default to empty hash';

  # _normalize_conninfo is an identity seam by default
  my $raw = [ { host => 'h' }, {} ];
  is $backend->_normalize_conninfo($raw), $raw,
    '_normalize_conninfo defaults to identity pass-through';
}

# ---------------------------------------------------------------------------
# _run_crud: select / select_single (via the pooled runner)
# ---------------------------------------------------------------------------
{
  my ($backend) = new_backend();

  $backend->{next_rows} = [ [ 1, 'Miles Davis' ], [ 2, 'John Coltrane' ] ];
  my $f = $backend->select_async('artist', [ 'artistid', 'name' ], { name => { -like => '%' } });
  isa_ok $f, 'Test::SyncFuture', 'select_async returns a Future';
  is_deeply [ $f->get ], [ [ 1, 'Miles Davis' ], [ 2, 'John Coltrane' ] ],
    'select_async resolves with the raw result rows';
  like $backend->_last_sql, qr/^SELECT .*FROM "artist"/,
    'select_async emitted real maker SQL for the artist table';

  # select_single resolves with only the first row
  $backend->{next_rows} = [ [ 1, 'Miles Davis' ], [ 2, 'John Coltrane' ] ];
  my $sf = $backend->select_single_async('artist', [ 'artistid', 'name' ], {});
  is_deeply scalar $sf->get, [ 1, 'Miles Davis' ],
    'select_single_async resolves with only the first row';

  # select_single on no rows resolves with undef
  $backend->{next_rows} = [];
  my $ef = $backend->select_single_async('artist', [ 'artistid' ], {});
  is scalar $ef->get, undef, 'select_single_async resolves undef when no rows';
}

# ---------------------------------------------------------------------------



( run in 1.206 second using v1.01-cache-2.11-cpan-f03e8824b8d )