146 lines
5.4 KiB
PHP
146 lines
5.4 KiB
PHP
<?php
|
||
|
||
namespace Tests\Unit;
|
||
|
||
use PHPUnit\Framework\TestCase;
|
||
use Services\DataBase\Classes\PdoDriver;
|
||
use Services\DataBase\Classes\Profiler;
|
||
use System\Classes\Config;
|
||
use System\Classes\MyException;
|
||
|
||
class PdoDriverTest extends TestCase
|
||
{
|
||
private PdoDriver $driver;
|
||
|
||
protected function setUp(): void
|
||
{
|
||
$config = Config::get('db', 'default')['connection'] ?? [];
|
||
$this->driver = new PdoDriver($config);
|
||
|
||
try {
|
||
$this->driver->pdo();
|
||
} catch (MyException $e) {
|
||
$this->markTestSkipped('Нет живого подключения к MariaDB (заполните App/config/config.local.php): ' . $e->getMessage());
|
||
}
|
||
|
||
$this->driver->exec('DROP TABLE IF EXISTS pdo_driver_test');
|
||
$this->driver->exec('CREATE TABLE pdo_driver_test (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255))');
|
||
}
|
||
|
||
protected function tearDown(): void
|
||
{
|
||
Profiler::reset();
|
||
|
||
if (isset($this->driver)) {
|
||
try {
|
||
$this->driver->exec('DROP TABLE IF EXISTS pdo_driver_test');
|
||
} catch (MyException) {
|
||
// соединения не было — нечего дропать
|
||
}
|
||
}
|
||
}
|
||
|
||
public function testFailoverThrowsWithAllHostErrors(): void
|
||
{
|
||
// Чисто логика failover — не требует живой БД, оба хоста заведомо недоступны
|
||
$driver = new PdoDriver(['hosts' => ['bad-host-1', 'bad-host-2'], 'dbname' => 'x', 'user' => 'x', 'password' => 'x']);
|
||
|
||
try {
|
||
$driver->pdo();
|
||
$this->fail('Ожидалось MyException');
|
||
} catch (MyException $e) {
|
||
$this->assertStringContainsString('bad-host-1', $e->getMessage());
|
||
$this->assertStringContainsString('bad-host-2', $e->getMessage());
|
||
}
|
||
}
|
||
|
||
public function testQueryInsertsAndSelects(): void
|
||
{
|
||
$this->driver->query('INSERT INTO pdo_driver_test (name) VALUES (?)', ['foo']);
|
||
$row = $this->driver->query('SELECT * FROM pdo_driver_test WHERE name = ?', ['foo'])->fetch();
|
||
|
||
$this->assertSame('foo', $row['name']);
|
||
}
|
||
|
||
public function testLastInsertId(): void
|
||
{
|
||
$this->driver->query('INSERT INTO pdo_driver_test (name) VALUES (?)', ['foo']);
|
||
$this->assertNotFalse($this->driver->lastInsertId());
|
||
}
|
||
|
||
public function testCommitPersistsChanges(): void
|
||
{
|
||
$this->driver->beginTransaction();
|
||
$this->driver->query('INSERT INTO pdo_driver_test (name) VALUES (?)', ['committed']);
|
||
$this->driver->commit();
|
||
|
||
$row = $this->driver->query('SELECT * FROM pdo_driver_test WHERE name = ?', ['committed'])->fetch();
|
||
$this->assertNotFalse($row);
|
||
}
|
||
|
||
public function testRollbackDiscardsChanges(): void
|
||
{
|
||
$this->driver->beginTransaction();
|
||
$this->driver->query('INSERT INTO pdo_driver_test (name) VALUES (?)', ['rolled_back']);
|
||
$this->driver->rollback();
|
||
|
||
$row = $this->driver->query('SELECT * FROM pdo_driver_test WHERE name = ?', ['rolled_back'])->fetch();
|
||
$this->assertFalse($row);
|
||
}
|
||
|
||
public function testNestedTransactionRollbackDoesNotAffectOuter(): void
|
||
{
|
||
$this->driver->beginTransaction();
|
||
$this->driver->query('INSERT INTO pdo_driver_test (name) VALUES (?)', ['outer']);
|
||
|
||
$this->driver->beginTransaction(); // SAVEPOINT sp_1
|
||
$this->driver->query('INSERT INTO pdo_driver_test (name) VALUES (?)', ['inner']);
|
||
$this->driver->rollback(); // ROLLBACK TO SAVEPOINT sp_1 — не трогает 'outer'
|
||
|
||
$this->driver->commit(); // фиксирует внешнюю транзакцию
|
||
|
||
$outer = $this->driver->query('SELECT * FROM pdo_driver_test WHERE name = ?', ['outer'])->fetch();
|
||
$inner = $this->driver->query('SELECT * FROM pdo_driver_test WHERE name = ?', ['inner'])->fetch();
|
||
|
||
$this->assertNotFalse($outer);
|
||
$this->assertFalse($inner);
|
||
}
|
||
|
||
public function testTransactionHelperRollsBackOnException(): void
|
||
{
|
||
try {
|
||
$this->driver->transaction(function (PdoDriver $driver) {
|
||
$driver->query('INSERT INTO pdo_driver_test (name) VALUES (?)', ['should_rollback']);
|
||
throw new \RuntimeException('boom');
|
||
});
|
||
$this->fail('Ожидалось RuntimeException');
|
||
} catch (\RuntimeException) {
|
||
// ожидаемо
|
||
}
|
||
|
||
$row = $this->driver->query('SELECT * FROM pdo_driver_test WHERE name = ?', ['should_rollback'])->fetch();
|
||
$this->assertFalse($row);
|
||
}
|
||
|
||
public function testManualPrepareExecuteIsProfiledToo(): void
|
||
{
|
||
// Регрессия: профилирование должно ловить любой execute(), а не только PdoDriver::query() —
|
||
// Repository сам делает $this->pdo->prepare()->execute() напрямую (см. get()/create()/...).
|
||
Profiler::reset();
|
||
|
||
$stmt = $this->driver->prepare('INSERT INTO pdo_driver_test (name) VALUES (?)');
|
||
$stmt->execute(['foo']);
|
||
|
||
$this->assertSame(1, Profiler::count());
|
||
}
|
||
|
||
public function testInTransaction(): void
|
||
{
|
||
$this->assertFalse($this->driver->inTransaction());
|
||
$this->driver->beginTransaction();
|
||
$this->assertTrue($this->driver->inTransaction());
|
||
$this->driver->commit();
|
||
$this->assertFalse($this->driver->inTransaction());
|
||
}
|
||
}
|