b38fe0c875
bootstrap: alias admin-db=UseAdminConnection; web.php: группа saas-admin теперь ['saas-admin','admin-db'] (swap default→pgsql_admin после гейта). Тест: admin-db в пайплайне /api/admin/tenants, saas-admin не потерян. SharesAdminPdo (зеркало SharesSupplierPdo) применён глобально к Feature suite (Pest.php): admin-db висит на всей группе → admin-эндпоинты в тестах читают через pgsql_admin (separate PDO) и не видели бы засеянные в транзакции данные; sharing PDO даёт cross-connection visibility. baseline: +trait.unused (Pest применяет трейт в рантайме, phpstan не видит uses() из Pest.php). 261 supplier+admin тестов зелёные. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
56 lines
2.9 KiB
PHP
56 lines
2.9 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace Tests\Concerns;
|
||
|
||
use Illuminate\Database\Connection;
|
||
use Illuminate\Support\Facades\DB;
|
||
|
||
/**
|
||
* Share PDO между pgsql и pgsql_admin connections в тестах.
|
||
*
|
||
* Зачем: middleware UseAdminConnection (alias admin-db) на группе saas-admin
|
||
* переключает default-подключение на pgsql_admin (роль crm_admin_user). В тестах
|
||
* DatabaseTransactions оборачивает каждый connection в свою транзакцию: данные,
|
||
* засеянные через default Tenant::factory() ($pgsql), не видны с pgsql_admin
|
||
* connection до commit'а → admin-эндпоинты в тестах видят 0 строк / 404.
|
||
* Sharing PDO означает — обе connection используют ту же PDO session → одну
|
||
* транзакцию, и засеянные данные видны admin-контроллеру.
|
||
*
|
||
* На production обе connection реальные separate PDO; pgsql_admin (srv_bypass)
|
||
* видит все тенанты по READ COMMITTED. Этот trait — только для test-окружения.
|
||
*
|
||
* Зеркало [[SharesSupplierPdo]] для pgsql_admin. Применяется глобально к Feature
|
||
* suite (см. tests/Pest.php), т.к. admin-db висит на всей группе saas-admin —
|
||
* любой admin-тест (текущий и будущий) получает cross-connection visibility без
|
||
* per-file opt-in. Для не-admin тестов инертен (pgsql_admin просто не запрашивают).
|
||
*/
|
||
trait SharesAdminPdo
|
||
{
|
||
protected function setUpSharesAdminPdo(): void
|
||
{
|
||
if (! config()->has('database.connections.pgsql_admin')) {
|
||
return;
|
||
}
|
||
|
||
$defaultConnection = DB::connection('pgsql');
|
||
$adminConnection = DB::connection('pgsql_admin');
|
||
$adminConnection->setPdo($defaultConnection->getPdo());
|
||
$adminConnection->setReadPdo($defaultConnection->getReadPdo());
|
||
|
||
// Синхронизируем уровень вложенности транзакции: DatabaseTransactions уже
|
||
// открыл транзакцию на pgsql (тот же PDO) к моменту setUp. Без синхронизации
|
||
// pgsql_admin считает transactions=0 и при ->transaction() зовёт
|
||
// PDO->beginTransaction() на уже активной транзакции → PDOException
|
||
// "There is already an active transaction" (например AdminTenantsController::
|
||
// updateBalance). С синхронизацией вложенный transaction() делает SAVEPOINT.
|
||
$level = $defaultConnection->transactionLevel();
|
||
if ($level > 0) {
|
||
$prop = new \ReflectionProperty(Connection::class, 'transactions');
|
||
$prop->setAccessible(true);
|
||
$prop->setValue($adminConnection, $level);
|
||
}
|
||
}
|
||
}
|