Files
XC_VM/tests/Unit/LazyDatabaseHandlerTest.php
T
Claude 0922f6423f feat(db): lazy database handle for the streaming endpoints
DatabaseHandler connects in its constructor, so every
DatabaseFactory::connect() in live/vod/timeshift and the shutdown handler
opened a MySQL connection to MAIN whether or not a query followed. The
cluster API plan counts every such connect before a node may leave the
legacy link.

LazyDatabaseHandler connects on the first query, simple_query, escape or
transaction and otherwise behaves like the eager handler, failure
included; DatabaseFactory::connectLazy() hands it out and keeps an
unopened one instead of replacing it. The streaming endpoints use it
(LazyDatabaseHandlerTest). Phase 0 seam.

Also replace call_user_func_array('parent::query', ...) in
DatabaseHandler::query() with parent::query(...func_get_args()): callable
strings using "parent" are deprecated since PHP 8.2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Es8UhyPS8MNjuYTagqfGWi
2026-09-25 10:07:46 +00:00

75 lines
2.6 KiB
PHP

<?php
use PHPUnit\Framework\TestCase;
use XcVm\Core\Database\LazyDatabaseHandler;
use XcVm\Infrastructure\Database\DatabaseFactory;
/**
* Streaming endpoints get a LazyDatabaseHandler: no MySQL connection to MAIN
* until the first query, so a request answered from cache or Redis opens none.
* The real connect goes through the xcvm_core extension; here it is replaced
* by an in-memory SQLite PDO that counts how often it is opened.
*/
final class LazyDatabaseHandlerTest extends TestCase {
private function handler(): LazyDatabaseHandler {
return new class extends LazyDatabaseHandler {
public int $rOpens = 0;
public function db_connect(bool $migrate = false, ?bool $graceful = null) {
$this->rOpens++;
$this->dbh = new \PDO('sqlite::memory:');
$this->dbh->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
$this->connected = true;
return true;
}
};
}
protected function tearDown(): void {
DatabaseFactory::reset();
unset($GLOBALS['db']);
}
public function testNothingOpensUntilTheFirstQuery(): void {
$rDb = $this->handler();
$this->assertFalse($rDb->isOpen());
$this->assertTrue($rDb->ping(), 'an unopened handle is usable, not dead');
$this->assertSame(0, $rDb->rOpens);
$this->assertTrue($rDb->query('SELECT 1 AS `one`'));
$this->assertSame(1, $rDb->rOpens);
$this->assertInstanceOf(\PDO::class, $rDb->dbh);
$rDb->query('SELECT ? AS `two`', 2);
$this->assertSame(1, $rDb->rOpens, 'opened once');
$this->assertTrue($rDb->isOpen());
}
public function testEscapeAndTransactionsOpenToo(): void {
$rDb = $this->handler();
$this->assertSame("'x'", $rDb->escape('x'));
$this->assertSame(1, $rDb->rOpens);
$rTx = $this->handler();
$this->assertTrue($rTx->beginTransaction());
$this->assertSame(1, $rTx->rOpens);
$rTx->rollback();
}
public function testConnectLazyKeepsAnUnopenedHandle(): void {
$GLOBALS['db'] = $this->handler();
DatabaseFactory::connectLazy();
$this->assertSame($GLOBALS['db'], DatabaseFactory::get());
$this->assertSame(0, $GLOBALS['db']->rOpens, 'reused, not replaced, not opened');
}
public function testStreamingEndpointsUseTheLazyHandler(): void {
$rRoot = dirname(__DIR__, 2) . '/src/';
foreach (['Public/stream/live.php', 'Public/stream/vod.php', 'Public/stream/timeshift.php', 'Streaming/Lifecycle/ShutdownHandler.php'] as $rFile) {
$rSource = (string) file_get_contents($rRoot . $rFile);
$this->assertStringContainsString('DatabaseFactory::connectLazy();', $rSource, $rFile);
$this->assertStringNotContainsString('DatabaseFactory::connect();', $rSource, $rFile);
}
}
}